Android SDK

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 an 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 it 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 functions:

Function Description
fun initialize(application: Application, notification: Notification, callback: MotionTag.Callback) Initializes the SDK with the Application context, Notification and the MotionTag.Callback implementation.
fun start() Starts tracking. It is recommended to set a valid user token prior to calling this function. The provided MotionTag.Callback implementation will be called to inform about SDK state changes or relevant tracking events.
fun stop() Stops tracking and removes the foreground notification.
fun 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).
fun clearData(onComplete: () -> Unit) Stops tracking and deletes all stored user data from the SDK. The callback function will be called on the main thread once the user data has been deleted.

It also contains the following public properties:

Property Description
var userToken: String? Updates the user token (JWT). The SDK expects a valid user token prior to executing the start() function. The provided token will be persisted internally.
var notification: Notification Updates the Notification that is displayed to the user when the device is moving. The FLAG_ONGOING_EVENT 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.
var wifiOnlyDataTransfer: Boolean Updates the setting that controls whether the data transmission only occurs when connected to a Wifi network or not. Its default value is false. Can be safely called even after the tracking has been started. The setting will be persisted internally.
val isTrackingActive: Boolean Returns true when tracking is active and collecting data.
val isBatteryOptimizationsEnabled: Boolean Returns true when battery optimizations is enabled for the app. This property might not work for device manufacturers with custom battery settings (e.g. Huawei, Xiaomi, etc).
val isPowerSaveModeEnabled: Boolean Returns true when power save mode is enabled in the device. This property might not work for device manufacturers with custom battery settings (e.g. Huawei, Xiaomi, etc).
val hasRequiredLocationSettings: Boolean Returns true when the device has all the required location settings (e.g.: LocationManager.GPS_PROVIDER).
val hasRequiredPermissions: Boolean Returns true when the app has been granted all required runtime permissions.
val requiredPermissions: List<String> Returns an list with the required runtime permissions.
val deniedRequiredPermissions: List<String> Returns an list with the required runtime permissions that are still denied.
val version: String Returns the current SDK version.

2.2 MotionTag.Callback interface

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

Function Description
fun onEvent(event: Event) Informs the application about a new incoming event.

The Event sealed class can be one of the following entities:

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(isSuccess: Boolean, 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(isSuccess: Boolean, 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.
BatteryOptimizationsChangedEvent(isEnabled: Boolean) Informs changes in the app's battery optimizations settings. The event might not work for device manufacturers with custom battery settings (e.g. Huawei, Xiaomi, etc).
PowerSaveModeChangedEvent(isEnabled: Boolean) Informs changes in the device's power save mode setting. The event might not work for device manufacturers with custom battery settings (e.g. Huawei, Xiaomi, etc).

2.3 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.
PERMISSION This is the case when tracking is started without the required runtime permissions.

3. Usage

3.1 Initialization

It is crucial that the SDK initialization happens as early as possible in the application’s onCreate function. This allows the SDK to automatically start when the app's process is created.

NOTE: Initializing the SDK after the onCreate function execution will result in app crashes and other unexpected behaviours.

class MyApp : Application(), MotionTag.Callback {

    private val yourNotification = buildNotification()
    private val motionTag = MotionTag.getInstance()

    override fun onCreate() {
        motionTag.initialize(this, yourNotification, this)
    }

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

    private fun buildNotification(): Notification {
        // https://developer.android.com/training/notify-user/build-notification
    }
}

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 an 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 Android 14

Starting on Android 14 (targetSdkVersion >= 34), a specific permission for foreground service must be declared in manifest:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />

More information about this permission in the official Android documentation.

3.2.6 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.7 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 Priority.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.

To create tokens in your backend, use the shared secret available under Authentication tokens

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 user token must be set first, otherwise the data transmission will fail.

class CustomClass {

    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 {

    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).
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.PERMISSION -> TODO()        // Do something after the tracking was restarted by grating back a required runtime permission.
        }
    }

    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.
            Reason.PERMISSION -> TODO()        // Do something after the application was stopped by revoking a required runtime permission.
        }
    }

    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.wifiOnlyDataTransfer = true
        motionTag.notification = yourNotification
    }
}

3.8 Clear stored user data

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

class CustomClass {

    private val motionTag = MotionTag.getInstance()

    fun clearUserData() {
        motionTag.clearData {
            // This callback will be called on the main thread once the deletion is completed.
        }
    }
}

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: 23
  • 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.