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 holdingandroid.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 MOTIONTAGsub
claim: the user UUIDexp
claim: expiry integer timestamp (optional)
Example payload:
{ "iss": "my-tenant-key", "sub": "aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb" }
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.
9. Compatibility and Known Issues
9.1 Compatibility with Android Gradle Plugin 8.x
Starting with Android Gradle Plugin 8.x (released in April 2023), R8 full mode is enabled by default. This change may affect your project if your ProGuard rules are not configured to accommodate the new default behavior.
Reference Links
Resolving Issues with R8 Full Mode
If you encounter issues related to the default R8 full mode, you can resolve them using one of the following approaches:
Update ProGuard Rules
Add the following rule to yourproguard-rules.pro
file:
proguard -keep,allowobfuscation,allowshrinking class *
Disable R8 Full Mode Add the following property to your
gradle.properties
file:
properties android.enableR8.fullMode=false