Android documentation

The MOTIONTAG Mobility & Location Analytics SDK enables to collect raw sensor data of the telephone in a battery efficient way. This data is then transmitted to the MOTIONTAG back-end system (ISO 27001 certified). In the backend, the sensor events are processed and a partial journey is constructed. Journeys consist either solely of tracks or tracks plus stays. Tracks describe a movement from a origin to a destination with a certain mode of transport. Stays symbolize a stationary behaviour with a particular purpose for the visit.

The use cases for the SDK are manifold. In the mobility sector it can be used to get detailed mobility data for planning purposes. The collected data enables to compare how the transport network is being used. This way the effectiveness of the current infrastructure and the passenger flow management is measured and the design of new mobility services. By implementing and using the SDK you can make use of these findings to improve timetables and routes, expand transport supply and attract more passengers.

If you integrate the MOTIONTAG SDK inside your own application, you can either download user journeys via a provided dump interface on the internet or we tailor a customized solution to your needs.

1. Download

1.1 Native sample app

A native Android sample app can be found on GitHub: MOTIONTAG/motiontag-sample-app-android. This sample app showcases the best practices in integrating the SDK into native Android apps.

1.2 Project build gradle

First you need to add the following to you projects build.gradle file:

allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url "https://pkgs.dev.azure.com/motiontag/releases/_packaging/releases/maven/v1" }
    }
}

This allows you to download the SDK from our public maven repository.

1.3 Application build gradle

The SDK requires compileSdkVersion 28 or higher on version 2.1.0 or older and compileSdkVersion 29 on version 2.2.0+.

Java 8 support is needed, therefore is should be added via compileOptions:

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }
}

It also requires apps to migrate to androidx, you can check the official migration guide here.

Lastly, you need to include the following dependency in your application build.gradle file to download the SDK:

implementation 'de.motiontag:tracker:<VERSION>'

1.4 Auto Backup for Apps

1.4.1 Android 11 and older

Auto Backup for Apps is a platform feature that automatically backs up a user's data from apps that target and run on Android 6.0 (API level 23) or later. To limit unexpected behavior from our SDK, you should either disable automated backups entirely or exclude the appropriate SDK files from full backup. To fully disable the Auto Backup feature you must add the following flag to the app's AndroidManifest.xml :

<application
    ...
    android:allowBackup="false"
    ... >
</application>

In case your app relies on Auto Backup and you don't want fully disable it, some SDK files must be excluded from the process:

<application
    ...
    android:allowBackup="true"
    android:fullBackupContent="@xml/my_backup_rules"
    ... >
</application>

The my_backup_rules.xml is a xml file that should be created in the app's resources folder and contains the paths that must be excluded from the back up process:

<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
    <exclude domain="sharedpref" path="motiontag_tracker.xml"/>
</full-backup-content>

The above lines can be appended to an existing file in case your app has already defined one. More information can be found here.

1.4.2 Android 12 and newer

Google has introduced a new format for the backup and restore configuration file for apps that run on and target Android 12. In the new format it is possible to specify different rules for the Cloud backups Device-to-device (D2D) transfers. To disable the backup for the SDK, please specify the following rules.

AndroidManifest.xml file:

<application
    ...
    android:allowBackup="true"
    android:dataExtractionRules="@xml/my_new_backup_rules"
    ... >
</application>

my_new_backup_rules.xml file:

<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
    <cloud-backup>
       <exclude domain="sharedpref" path="motiontag_tracker.xml"/>
    </cloud-backup>
    <device-transfer>
        <exclude domain="sharedpref" path="motiontag_tracker.xml"/>
    </device-transfer>
</data-extraction-rules>

More information can be found here.

1.5 SDK version

You can find the SDK version in our changelog page.

You should be good to go now ;)

2. MotionTag Tracker Structure

2.1 MotionTag class

The MotionTag class is the main entry point of the SDK. It has the following public methods:

Returns Method Explanation
void initialize(application: Application, settings: Settings, callback: MotionTag.Callback) Initializes the SDK with the Application context, Settings and the MotionTag.Callback implementation.
void start() Starts tracking. It throws an IllegalStateException exception if no user token is specified. The provided MotionTag.Callback implementation will be called to inform about SDK state changes or relevant tracking events.
void stop() Stops tracking and removes the foreground notification.
void userToken(token) Updates the user JWT. The SDK expects this function to be called at least once before executing the start() function. The provided token will be persisted internally.
Boolean isTrackingActive() Returns true if the tracking is active and collecting data, false otherwise.
void notification(notification: Notification) Updates the provided tracking Notification. Can be safely called even after the tracking has been started.
void useWifiOnlyDataTransfer(on: Boolean) Enables the data transmission only when connected to Wifi. Can be safely even called after the tracking has been started.
Boolean hasRequiredLocationSettings() Checks if the device has all the required location settings (e.g.: LocationManager.GPS_PROVIDER).
Boolean hasRequiredPermissions() Checks if the app has been granted all required runtime permissions.
List<String> getRequiredPermissions() Returns an list with the required runtime permissions.
List<String> getDeniedRequiredPermissions() Returns an list with the required runtime permissions that are still denied.
void requestRequiredLocationSettings(activity: Activity, requestCode: Int) Displays a dialog that allows users to enable all the required location settings. The result of the dialog will be returned via onActivityResult(requestCode: Int, resultCode: Int, data: Intent).
Settings getSettings() Retrieves current settings
String? getUserToken() Retrieves the current user token or null if not specified yet.
void clearData() Stops tracking and deletes all stored user data from the SDK

2.2 Settings class

The Settings class is used to customise the behaviour of the SDK during its initialization.

Type Parameter Requirement Explanation
android.app.Notification notification Required The Android Notification to be displayed when the device is moving. The FLAGONGOINGEVENT flag will be added to the Notification if not provided. Starting from API version 26, a NotificationChannel must be created first. In order to avoid distracting the user, we recommend setting NotificationCompat.PRIORITY_LOW and NotificationManager.IMPORTANCE_LOW to your Notification and the associated NotificationChannel, respectively.
Boolean isWifiOnlyDataTransfer Optional Enables WiFi only transmission of sensor events. Disabling this can lead to high cost because of the mobile data usage, however it improves analysis speed, so the user has maybe a faster response of processed journeys. The default is true.

2.3 MotionTag.Callback interface

MotionTag.Callback interface used to inform an application about state changes:

Returns Method Explanation
void onEvent(event: Event) Informs the application about a new incoming event.

Event description:

Event Explanation
AutoStartEvent(reason: Reason) Informs the application that tracking has been automatically started.
AutoStopEvent(reason: Reason) Informs the application that tracking has been automatically stopped.
LocationEvent(location: Location) Hands the latest captured android.location.Location. to the application.
TransmissionEvent.Success(timestamp: Long, trackedFrom: Long, trackedTo: Long) Informs when a package of events has been successfully sent to the server. The timestamp represents the time of the server confirmation response. The trackedFrom and trackedTo represents the time range of the transmitted events.
TransmissionEvent.Error(timestamp: Long, errorCode: Int, errorMessage: String) Informs when a package of events failed to be transmitted to the server. The timestamp represents the time of the failure. The errorCode and errorMessage describes the error that occurred.

2.4 Reason enum

The Reason enum describes the reasons behind the automatic tracking stops and starts.

Selection Explanation
RESTART This is the case when tracking is restarted after a phone restart.
LOCATION_SERVICE This is the case when tracking is restarted after the user turns on the location services.
KILLED This is the case when tracking is restarted after an application kill.
PERMISSION This is the case when tracking is started without the required runtime permissions.

3. Usage

3.1 Initialization

The SDK initialization should happen in the Application's onCreate function, this allows the SDK to automatically start when the app's process is created.

class MyApp : Application(), MotionTag.Callback {

    private val yourNotification: Notification // https://developer.android.com/training/notify-user/build-notification

    override fun onCreate() {
        val settings = Settings(yourNotification, isWifiOnlyDataTransfer = false)
        val motionTag = MotionTag.getInstance()
        motionTag.initialize(this, settings, this)
    }

    override fun onEvent(event: Event) {
        TODO() //Handle events from the SDK
    }
}

3.2 Runtime permissions

3.2.1 Android 9 and below

The SDK needs the android.permission.ACCESS_FINE_LOCATION runtime permission.

3.2.2 Android 10

The SDK needs android.permission.ACCESS_FINE_LOCATION permission and since Android 10 both android.permission.ACCESS_BACKGROUND_LOCATION and android.permission.ACTIVITY_RECOGNITION are also needed.

After requesting the location permissions, Android 10 users will get a dialog with 3 different options to choose from. Users must select "Allow all the time" option, otherwise the SDK won't function properly.

3.2.3 Android 11

Starting on Android 11, the android.permission.ACCESS_FINE_LOCATION permission must be granted before requesting android.permission.ACCESS_BACKGROUND_LOCATION. There are 2 different cases to consider when running the SDK on a Android 11 device:

  • If the app targets Android 11 (targetSdkVersion = 30): A system exception will be thrown if android.permission.ACCESS_BACKGROUND_LOCATION is requested before holding android.permission.ACCESS_FINE_LOCATION permission.
  • If the app targets Android 10 or lower (targetSdkVersion <= 29): The app can still request both location permissions at the same time. The resulting permission dialog will contain a link that allows users to select "Allow all the time" in the app permission settings.

More information in the official Android documentation. An example implementation can be found in our sample app.

3.2.4 Android 12

Apps targeting Android 12 must request both android.permission.ACCESS_COARSE_LOCATION and android.permission.ACCESS_FINE_LOCATION in the same permission request. This will allow users to select the new precise location setting which is required by the SDK.

3.2.5 Runtime permission helper functions

The SDK exposes 3 public methods that helps you verify whether the SDK has been granted or not all required runtime permissions based on the device's Android version:

hasRequiredPermissions(): Bool, getRequiredPermissions(): List<String>, getDeniedRequiredPermissions(): List<String>.

3.2.6 Background location access approval

In 2020, Google introduced a new process that requires app developers to get explicit approval for each app that needs access to user’s location data in the background. App developers integrating the Android MOTIONTAG SDK will need to get this approval since the SDK requires access to background location data in order to function correctly. You can find more information about the approval here.

3.3 Location settings

Location services should be enabled in the device otherwise the SDK will not function properly. In order to display a dialog requesting the users to enable it, one could use the SettingsClient and set LocationRequest.PRIORITY_HIGH_ACCURACY priority in the LocationSettingsRequest.Builder.

The SDK contains 2 methods to help you with the task of verifying and requesting the required location settings: boolean hasRequiredLocationSettings() and requestRequiredLocationSettings(Activity activity, int requestCode).

3.4 SDK user authentication

The SDK must be configured at runtime with a user-specific token.

Tokens can be generated on your backend, or manually with the form below (only accessible when signed in). They are signed JWTs (see jwt.io). Users are identified by distinct UUIDs – the creation and management of the user UUIDs is up to you. MOTIONTAG creates a user entry in its database when data from the SDK for a new user UUID arrives for the first time.

To generate the JWTs on your backend, encode and sign a payload like the example below with the shared secret, which is accessible in the admin dashboard under "Authentication tokens". Include these claims:

  • iss claim: the tenant key that you have received from MOTIONTAG
  • sub claim: the user UUID
  • exp claim: expiry integer timestamp (optional)

Example payload:

{
  "iss": "my-tenant-key",
  "sub": "aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb"
}

Generate JWTs from user UUIDs

Only accessible when signed in. You need a custom domain setup by MOTIONTAG (e.g. my-tenant-key.motion-tag.de) and an account to sign in. Contact MOTIONTAG Support to request a custom domain setup and an account. If you already have both, visit your custom domain and sign in to see a customized version of this documentation.

3.5 Start/Stop Tracking

As described in the chapter 'SDK user authentication', to start tracking, you need a token for the SDK to access our backend system.

IMPORTANT: This token is user-specific and should not be used on multiple devices at the same time.

The token must be set before calling starting for the first time otherwise it will throw an IllegalStateException:

class CustomClass : MotionTag.Callback {

    private val token = "SECRET_USER_JWT"
    private val motionTag = MotionTag.getInstance()

    fun start() {
        motionTag.userToken(token)
        motionTag.start()
    }
}

You can stop tracking at any time:

class CustomClass : MotionTag.Callback {

    private val motionTag = MotionTag.getInstance()

    fun stop() {
        motionTag.stop()
    }
}

The user token and the SDK state will be persisted internally. The SDK will automatically resume tracking after a device reboot or an app update.

3.6 Automatic Start/Stop

When the tracking is ACTIVE it will be automatically stopped and in some cases automatically restarted e.g.

  • Location Services Off causes an automatic stop (Reason.LOCATION_SERVICE).
  • Location Services On causes an automatic start (Reason.LOCATION_SERVICE).
  • Reboot causes an automatic start (Reason.RESTART).
  • Application Update causes an automatic start (Reason.RESTART).
  • Missing a required permission causes an automatic stop (Reason.PERMISSION).
  • System or user kills the App causes an automatic start. If the App is manually opened by the user after the killing, the tracking will be immediately started again. Otherwise, the tracking will be automatically restarted at most 20 minutes. (Reason.KILLED).
class CustomClass : MotionTag.Callback {

    override fun onEvent(event: Event) {
        when (event) {
            is AutoStartEvent -> onAutoStart(event.reason) // do something after the tracking was restarted, e.g.
            is AutoStopEvent -> onAutoStop(event.reason)   // do something after the tracking was stopped, e.g.
            is TransmissionEvent -> onTransmission(event)  // do something after the application has successfully transmitted data, or an error occurred in the process e.g.
            is LocationEvent -> onLocation(event.location) // do something after the application provides a location, e.g.
        }
    }

    private fun onAutoStart(reason: Reason) {
        when (reason) {
            Reason.RESTART -> TODO()           // do something after the tracking was restarted after rebooting the phone or updating the application.
            Reason.LOCATION_SERVICE -> TODO()  // do something after the tracking was restarted by turning on the location services.
            Reason.KILLED -> TODO()            // do something after the application has been killed and the tracking is restarted.
        }
    }

    private fun onAutoStop(reason: Reason) {
        when (reason) {
            Reason.RESTART -> TODO()            // do something after the tracking was stopped because the phone was shut down
            Reason.LOCATION_SERVICE -> TODO()   // do something after the tracking was stopped by turning off the location services.
        }
    }

    private fun onTransmission(event: TransmissionEvent) {
        when(event) {
            is TransmissionEvent.Success -> TODO()   // Transmission was successful. Maybe display the time, so the user gets informed that data will be analysed soon.
            is TransmissionEvent.Error -> TODO()     // Transmission was not successful. Maybe log the error in order to analyze it afterwards.
        }
    }

    private fun onLocation(location: Location) {
        // Maybe display the location on a map
    }
}

3.7 Runtime Setting Changes

You can still change all settings during runtime. For example if the user changes the language settings on the phone you probably want to provide a new notification with the appropriate language. Just call one of the following methods from anywhere in your application.

class CustomClass : MotionTag.Callback {

    private val yourNotification: Notification // https://developer.android.com/training/notify-user/build-notification
    private val motionTag = MotionTag.getInstance()

    fun customize() {
        motionTag.useWifiOnlyDataTransfer(true)
        motionTag.notification(yourNotification)
    }
}

3.8 Current Settings

You can get current SDK settings with the following call:

class CustomClass : MotionTag.Callback {

    private val motionTag = MotionTag.getInstance()

    fun getCurrentSettings() : Settings {
        return motionTag.getSettings()
    }
}

3.9 Clear stored user data

It is possible to delete user data that is stored locally with the following command...

class CustomClass : MotionTag.Callback {

    private val motionTag = MotionTag.getInstance()

    fun clearUserData() {
        motionTag.clearData()
    }
}

4. WorkManager

The MOTIONTAG SDK uses the WorkManager library under the hood to schedule its deferrable and asynchronous tasks. Please avoid calling WorkManager.cancelAllWork() at all costs as this will cancel all workers previously scheduled by the SDK.

5. Non-standard background process limitations

Some Android OEMs, like Huawei and OnePlus, decided to implement non-standard background process limitations on 3rd party apps as an attempt to reduce battery consumption. The MOTIONTAG SDK must be running all times in the background, otherwise it won't function properly.

Therefore we recommend developers integrating our SDK to read the https://dontkillmyapp.com website, it describes this problem in detail and it provides some workaround options for both developers and users. There's also this StackOverflow post which describes how developers can forward users to the correct OEM settings.

When battery optimization is turned on for your app, the MOTIONTAG SDK may not be able to track and generate data continuously. If 24/7 tracking on all supported phone models is crucial to your use case, we strongly recommend you to include a prompt for the user, and facilitate the deactivation of battery optimization settings for your app on the affected phones.

6. Gradle

  • Minimum SDK Version: 21
  • Obfuscated with R8

7. License

The SDK is licensed under the MOTIONTAG SDK Test License.

8. Open Source Software Licenses

The SDK relies on Open Source Software components. See their respective Open Source Licenses.