Using the SDK

Identity

The host app shares the identity of its currently authenticated user by sharing an Identity object with the SDK. An Identity is constructed with the host app's API Key and the host app's unique user identifier.

val identity = Identity(MY_SDK_API_KEY, firebaseAuth.currentUser?.uid)

ImsSdkManager.setIdentity(context, identity)

The SDK uses this Identity when communicating with the DriveSync server.

Phone Registration (aka Device Activation)

The user's phone must be registered through the Portal before trips recorded on that phone can be uploaded. Depending on the activation method, as selected by the host app, the device may only require one activation.

There is currently a limit of one registered phone per user account. If the same user logs in and registers a second phone, trips from the first phone will no longer get uploaded to the server.

Use the DeviceService to activate the device (register the phone) before you start recording trips.

/**********************
 * Activate the device.
 **********************/
// The identity of the user.
Identity identity = new Identity("HOST-APP-API-KEY", "HOST-APP-USER-IDENTIFIER");
DeviceService deviceService = new DeviceService(identity);
deviceService.activate(DeviceService.DeviceActivationMode.ACTIVATE, new ResultCallback<Device>() {
    @Override
    public void execute(Result<Device> result) {
        switch (result.getType()) {
            case FAILURE:
                Log.e("TAG", "Something went wrong.");
                break;
            case SUCCESS:
                Log.i("TAG", "Device is activated.");
                break;
        }
    }
});

Phone Identifier

On every install the phone gets a unique ID for sending data to our servers. This means if the application is re-installed a new ID is created. Note that the ID creation is automatic on every install.

The SDK has the ability to refresh this unique ID through an API without the need for app reinstallation:

ImsSdkManager.changePhoneId(context)

After refreshing the phone identifier, the new ID will need to be registered.

Controlling How Files are Uploaded

The host app has the option to set how to upload trip files to DriveSync server. This can be done at initialization or post-initialization (i.e. as a toggle feature).

The SDK can set the app to upload files over any network or WiFi only. The default setting for this option is false which means files will be uploaded over any network.

See below example for setting the upload method only over WiFi at initialization:

// Initial configuration
ImsSdkManager.setSdkConfiguration(context, 
    ImsSdkManager.Builder()
        .setUploadWiFiOnly(true)
        // Other configuration options
        .build() )

// Changes at runtime
ImsSdkManager.setUploadWiFiOnly(context, true)

// Current setting
val isWiFiOnly = ImsSdkManager.configuration.isUploadWiFiOnly

Heartbeat Service

This feature is deprecated as of 1.17

The Heartbeat service is used to provide the server with regular information about the state of each phone, whether or not any trips are reported.

Once started, the heartbeat repeats automatically at the specified rate, typically once a day. Heartbeat data is only sent when there is a network connection, and the actual interval between messages may vary.

Note: This service uses the WorkManager; see previous notes about Custom initialization.

// Initial configuration
ImsSdkManager.setSdkConfiguration(context, 
    ImsSdkManager.Builder()
        .setHeartbeatInterval(24)    // Hours
        // Other configuration options
        .build() )

// Enable at runtime
ImsSdkManager.setHeartbeatInterval(context, 24)

// Disable at runtime
ImsSdkManager.setHeartbeatInterval(context, 0)

WorkManager runtime

The SDK uses the Android WorkManager for many background operations. The SDK works with either the default or a custom WorkManager configuration. If there is any issues regarding WorkManager initialization, please take a look at known issues.

Custom WorkManager

If the host application is using a customized WorkManager initialization step (i.e, using a WorkerFactory), then the host application must implement the getWorkManagerConfiguration using the DelegatingWorkerFactory. Using other factories will prevent the SDK from initializing important components (notably related to uploading trips).

    @NonNull
    @Override
    public Configuration getWorkManagerConfiguration() {
        DelegatingWorkerFactory delegatingFactory = new DelegatingWorkerFactory();
        delegatingFactory.addFactory(hostAppWorkerFactory);
        return new Configuration.Builder()
                .setWorkerFactory(delegatingFactory)
                .build();
    }

The factory added to the DelegatingWorkerFactory must return null when createWorker is called with a class that is not part of the ones expected by the host application. This allows the delegating factory to fall back to other factories (including the ones from the SDK). If your factory returns null, there is no need to explicitly add the SDK worker factory.

In the following example, the host application uses a custom factory because the HostApplicationWorker requires a special constructor. When the createWorker class is called, it returns a value *only if the object created is expected by the host application.

class Factory constructor(private val something: SomeClassNeededByTheFactory) : WorkerFactory() {
        override fun createWorker(appContext: Context, workerClassName: String, workerParameters: WorkerParameters): ListenableWorker? {
            return when(workerClassName) {
                HostApplicationWorker::class.java.name ->
                    HostApplicationWorker(
                            appContext,
                            workerParameters,
                            something)
                else ->
                    null
            }
        }
    }

A custom WorkManagerInitializer must also be created so that the custom WorkManager initializes before the SDK. To do this implement the following:

  • Create WorkManagerInitializer

public class WorkManagerInitializer : Initializer<WorkManager> {
    override fun create(context: Context): WorkManager {
        // NOTE: If your app uses a Custom WorkManager, initialize it here.
        val configuration = Configuration.Builder().build()
        try {
            WorkManager.initialize(context, configuration)
        } catch (e: Exception){
            Log.i("WorkManagerInitializer", "WorkManager initialization threw an error; likely already initialized. Error: $e")
        }
        return WorkManager.getInstance(context)
    }
    override fun dependencies(): List<Class<out Initializer<*>>> {
        // No dependencies on other libraries.
        return emptyList()
    }
}
  • Create a Custom App Initializer

public class AppInitializer : Initializer<Any> {
    override fun create(context: Context) {
        ImsSdkInitializer().create(context)
        ImsUploadManagerInitializer().create(context)
    }

    override fun dependencies(): MutableList<Class<out Initializer<*>>> {
        return mutableListOf(WorkManagerInitializer::class.java)
    }
}
  • Add the AppInitializer, WorkManagerInitializer, and Disable the SDK Initializers

    • NOTE: Do not forget to replace the packageId for the WorkManagerInitializer and AppInitializer

<provider android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge" >
    <meta-data
        android:name="androidx.work.WorkManagerInitializer"
        android:value="androidx.startup"
        tools:node="remove" />
    <meta-data android:name="com.intellimec.mobile.android.common.ImsSdkInitializer"
        android:value="androidx.startup"
        tools:node="remove" />
    <meta-data android:name="com.intellimec.mobile.android.gateway.ImsUploadManagerInitializer"
        android:value="androidx.startup"
        tools:node="remove"  />
    <meta-data android:name="{yourPackageId}.WorkManagerInitializer"
        android:value="androidx.startup" />
    <meta-data android:name="{yourPackageId}.AppInitializer"
       android:value="androidx.startup" />
</provider>

Diagnostics and Logging

The SDK logs its status through:

  • Info, Warning, and Error messages in the Android system log

  • Trip recorder diagnostic information uploaded to the DriveSync server along with trip files

To assist in during development and testing, DsLogManager provides a way for SDK users to access the SDK's internal log messages. These include debug level messages that are not normally logged.

SDK users can access the logs by registering a log listener with the SDK. The log listener has a single method which is used to report all log messages:

public fun log(level: DsLogLevel, source: String?, message: String)

For example, SDK log messages can be added to Crashlytics reports using the following code:

 ImsSdkManager.addLogListener(object : DsLogListener {
    override fun log(level: DsLogLevel, source: String?, message: String) {
        FirebaseCrashlytics.getInstance().log(formatLogInfo(level, source, message))
    }
})

The SDK provides some standard listeners to assist developers:

Log File Listener

DsLogFileListener writes log messages as plain text to local storage on the Android phone. A new file is created for each day; files older than the specified number of days are automatically deleted.

The SDK Sample App shows how to use this listener to record log messages and display them using an external app:

ImsSdkManager.addLogListener(DsLogFileListener(DsLogLevel.DEBUG, File(application.getFilesDir(), "drivesync/log"), 7))

Note that for Android 11 you need to use the FileProvider API and explicitly grant read permission so that other apps can read the log files. This is not necessary if you implement your own file viewer inside your app.

Unit Test Log Listener

For unit tests, DsUnitTestLogListener routes log messages to the console. This is especially helpful when used to replace the default log system instead of mocking it:

ImsSdkManager.setLogListener(DsUnitTestLogListener())

The unit test log listener class implementation is extremely simple. The complete source is:

public class DsUnitTestLogListener: DsLogListener {
    public override fun log(level: DsLogLevel, source: String?, message: String) {
       println("${level.name}: $source: " + message)
    }
}

Log File Upload

The SDK automatically uploads diagnostic information to the DriveSync server. This includes log files with Info, Warning, and Error messages only; Debug messages are never uploaded to the server.

No PII (Personal Identifiable Information) is included in the uploaded logs.

Logs are uploaded

  1. At the end of every trip

  2. Every 24 hours (by default)

You can request the SDK to upload logs more frequently if desired while debugging.

// During configuration
ImsSdkManager.setSdkConfiguration( context,
    ImsSdkManager.Builder()
        .setLogUploadInterval(1)    // Every hour
        // Moreconfiguration
        .build() )
        
// At runtime
ImsSdkManager.setLogUploadInterval(context, 1)     

Diagnostic Mode

Additional diagnostic features can be enabled by setting the global diagnostic mode flag:

ImsSdkManager.setDiagnosticMode(true)

This property is not persisted; diagnostic mode must be enabled programmatically every time the app runs.

Functions:

  • Throws an exception for some error cases that are simply logged in normal mode.

  • Provides detailed network logging messages for portal calls

  • Provides additional information during Bluetooth device operations

Last updated