TLKit
This document contains a quick start guide for integrating TLKit into your Android application. More detailed documentation can be found in the Online documentation and API docs.
Sample Project
Checkout our sample project AndroidTLKitExample for a simple working example of how developers can use TLKit.
Integrating TLKit into a project
1 / Add the TLKit library
Modify the project build.gradle
file as follows.
Add the TLKIT artifact's repository
repositories {
maven{ url 'https://raw.githubusercontent.com/tourmalinelabs/AndroidTLKitSDK/master'}
}
Add this repository section directly at the top level of the build.gradle
and not under the buildscript
section.
Add the TLKit as a dependency.
dependencies {
compile ("com.tourmalinelabs.android:TLKit:11.0.17121101@aar") { transitive=true }
}
The transitive directive allows your project to automatically include the TLKIT dependencies.
2 / Add user permissions
Manifest Permissions
Add the following the following permissions to the project Manifest.xml
.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
....
</manifest>
Requesting permissions in app
For Android versions 6 and above the application also needs to explicitly ask
for a set of permissions from the user. Engine.MissingPermissions
provides a
list of the required permissions that can be passed to
ActivityCompat.requestPermissions
Using TLKit
The heart of the TLKit is the Context Engine. The engine needs to be initialized with some user information and a drive detection mode in order to use any of its features.
User information
There are two types of user information that can be used to initialize the engine: 1. A SHA-256 hash of some user id. Currently only hashes of emails are allowed but other TL approved ids could be used. 2. An application specific username and password.
The first type of user info is appropriate for cases where the SDK is used only for data collection. The second type is useful in cases where the application wants to access the per user information and provide password protected access to it to it's users.
Automatic and manual modes
The engine can be initialized for either automatic drive detection where the SDK will automatically detect and monitor drives or a manual drive detection where the SDK will only monitor drives when explicitly told to by the application.
The first mode is useful in cases where the user is not interacting with the application to start and end drives. While the second mode is useful when the user will explicitly start and end drives in the application.
Engine is a foreground service
The engine is an Android foreground service. All foreground services are permanently displayed in the device notification area. For this reason it needs to be initialized with a Notification object to inform the user about the purpose of the application you are building. The notification is created with the following code and must be given at the engine initialization:
final String NOTIF_CHANNEL_ID = "background-run-notif-channel-id";
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
final NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
final NotificationChannel channel = new NotificationChannel(NOTIF_CHANNEL_ID, getText(R.string.foreground_notification_content_text), NotificationManager.IMPORTANCE_NONE);
channel.setShowBadge(false);
notificationManager.createNotificationChannel(channel);
}
final Notification note = new NotificationCompat.Builder(this, NOTIF_CHANNEL_ID)
.setContentTitle(getText(R.string.app_name))
.setContentText(getText(R.string.foreground_notification_content_text))
.setSmallIcon(R.mipmap.ic_foreground_notification)
.setPriority(NotificationCompat.PRIORITY_MIN)
.build();
Example initialization with SHA-256 hash in automatic mode
The below examples demonstrate initialization with just a SHA-256 hash. The example application provides code for generating this hash.
Once started in this mode the engine is able to automatically detect and record all drives.
Engine.Init( getApplicationContext(),
ApiKey,
HashId( "androidexample@tourmalinelabs.com" ),
true, // set to `false` for manual mode
note,
new CompletionListener() {
@Override
public void OnSuccess() {}
@Override
public void OnFail( int i, String s ) {} );
Starting a new drive
In manual mode you can start a new drive by calling:
final UUID uuid = ActivityManager.StartManualTrip();
When starting a new drive you receive an id in return.
Stoping a drive
For stopping a drive you need to call StopManualTrip with the right id.
ActivityManager.StopManualTrip(uuid);
Note that until a drive is explicitly stopped it will continue to record data. Even if the SDK is restarted, it will continue to record data for any trips that it was recording.
Current manual drives
The application can query the list of any drives being required as follows.
final ArrayList<Drive> manualDrives = ActivityManager.ActiveManualDrives();
Destroying the engine
Once initialized there is no reason to destroy the Engine
unless you need to
set a new AuthMgr
for a different user or need to switch between manual and
automatic modes. In those cases, the engine can be destroyed as follows:
Engine.Destroy(getApplicationContext(),
new CompletionListener() {
@Override
public void OnSuccess() {
Log.d(TAG, "Engine destroyed.");
}
@Override
public void OnFail( int i, String s ) {
Log.e(TAG, "Engine destruction failed.");
}
});
Listening for Engine state changes
In addition to the completion listeners the engine also locally
broadcasts lifecycle events which can be subscribed to via the
Engine.ACTION_LIFECYCLE
action as shown below.
It is recommended to use this intent to register any listeners with the engine. This is because in event of an unexpected application termination the lifecycle registered listeners will be lost. When the engine automatically restarts with the app the intent will be the only notification that the engine is running.
final LocalBroadcastManager mgr = LocalBroadcastManager.getInstance((getApplicationContext());
mgr.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent i) {
int state = i.getIntExtra("state", Engine.INIT_SUCCESS);
switch (state) {
case Engine.INIT_SUCCESS: {
Log.i(LOG_AREA, "ENGINE INIT SUCCESS");
registerActivityListener();
registerLocationListener();
break;
}
case Engine.INIT_REQUIRED: {
Log.i(LOG_AREA, "ENGINE INIT REQUIRED: Engine " +
"needs to restart in background...");
final Monitoring.State monitoringState =
Monitoring.getState(getApplicationContext());
final CompletionListener listener = new CompletionListener() {
@Override
public void OnSuccess() {
}
@Override
public void OnFail(int i, String s) {
}
};
switch (monitoringState) {
case AUTOMATIC:
initEngine(true, listener);
break;
case MANUAL:
initEngine(false, listener);
break;
default:
break;
}
break;
}
case Engine.INIT_FAILURE: {
final String msg = i.getStringExtra("message");
final int reason = i.getIntExtra("reason", 0);
Log.e(LOG_AREA, "ENGINE INIT FAILURE" + reason + ": " + msg);
break;
}
case Engine.GPS_ENABLED: {
Log.i(LOG_AREA, "GPS_ENABLED");
break;
}
case Engine.GPS_DISABLED: {
Log.i(LOG_AREA, "GPS_DISABLED");
break;
}
case Engine.LOCATION_PERMISSION_GRANTED: {
Log.i(LOG_AREA, "LOCATION_PERMISSION_GRANTED");
break;
}
case Engine.LOCATION_PERMISSION_DENIED: {
Log.i(LOG_AREA, "LOCATION_PERMISSION_DENIED");
break;
}
case Engine.POWER_SAVE_MODE_DISABLED: {
Log.i(LOG_AREA, "POWER_SAVE_MODE_DISABLED");
break;
}
case Engine.POWER_SAVE_MODE_ENABLED: {
Log.i(LOG_AREA, "POWER_SAVE_MODE_ENABLED");
break;
}
}
},
new IntentFilter(Engine.ACTION_LIFECYCLE));
}
Drive monitoring API
Listeners can be registered to receive Drive events.
Registering for drive events
Register a drive listener can be done as follows:
ActivityListener listener = new ActivityListener() {
@Override
public void OnEvent( ActivityEvent e ) {
Log.d("ActivityListener", "Activity event received: " + e );
}
@Override
public void RegisterSucceeded() {
Log.d("ActivityListener", "registered!" );
}
@Override
public void RegisterFailed( int e ) {
Log.e("ActivityListener", "register failed w/reason " + reason + " :)" );
}
};
ActivityManager.RegisterDriveListener(listener);
Multiple listeners can be registered via this API and all listeners will received the same events.
Note: Multiple events may be received for the same drive as the drive progresses and the drive processing updates the drive with more accurate map points.
To stop receiving drive monitoring unregister the listener as follows:
ActivityManager.UnregisterDriveListener(listener);
Querying drive history
Some amount of drives are available for querying as follows.
ActivityManager.GetDrives( new Date(0L),
new Date(),
20,
new QueryHandler<ArrayList<Drive>>() {
@Override
public void Result( ArrayList<Drive> drives ) {
Log.d("DriveMonitor", "Recorded drives:");
for (Drive drive : drives) {
Log.d("DriveMonitor", drive.toString());
}
}
@Override
public void OnFail( int i, String s ) {
Log.e("DriveMonitor", "Query failed with err: " + i );
}
});
Location monotioring API
TLKit provides lower power location updates than traditional GPS only solutions.
Registering for location updates
A listener can be registered as follows.
LocationListener listener = new LocationListener() {
@Override
public void OnLocationUpdated (Location l) {
Log.d("Location listener", "Location received: " + l );
}
@Override
public void RegisterSucceeded () {
Log.d("Location listener", "registered! ");
}
@Override
public void RegisterFailed (int reason) {
Log.e("Location listener",
"register failed w/reason " + reason + " :)" );
}
};
LocationManager.RegisterLocationListener(listener);
A listener can be unregistered as follows:
LocationManager.UnregisterLocationListener(listener);
Querying location history
TLKit provides the ability to query past locations via
QueryLocations
method of the Engine. These can be used as follows:
LocationManager.QueryLocations(0L, Long.MAX_VALUE, 20, new QueryHandler<ArrayList<Location>>() {
@Override
public void Result(ArrayList<Location> locations) {
Log.d( "QueryLocations", "Recorded locations" );
for( Location location: locations ) {
Log.d("QueryLocations", location.toString() );
}
}
@Override
public void OnFail(int i, String s) {
Log.e("QueryLocations", "Query failed with err: " + i );
}
});
Telematics monotoring API
TLKit provides the possibility to monitor telematics events occuring during a drive.
Registering for telematics updates
A listener can be registered as follows.
TelematicsEventListener telematicsListener = new TelematicsEventListener() {
@Override
public void OnEvent(TelematicsEvent e) {
Log.d( LOG_AREA, "Got telematics event: " + e.getTripId() +
", " + e.getTime() + ", " + e.getDuration() );
}
@Override
public void RegisterSucceeded() {
Log.d(LOG_AREA, "startTelematicsListener OK");
}
@Override
public void RegisterFailed(int i) {
Log.d(LOG_AREA, "startTelematicsListener KO: " + i);
}
};
ActivityManager.RegisterTelematicsEventListener(telematicsListener);
A listener can be unregistered as follows:
ActivityManager.UregisterTelematicsEventListener(telematicsListener);
Querying telematics history
TLKit provides the ability to query past telematics via
GetTelematicsEvents
method of the Engine. These can be used as follows:
ActivityManager.GetTelematicsEvents(new Date(-24*60*60*1000), new Date(), 1, 100, new PaginatedQueryHandler<ArrayList<TelematicsEvent>>() {
@Override
public void Result(final int currentPage, final int pageCount, final int resultCount, ArrayList<TelematicsEvent> res) {}
@Override
public void OnFail(int code, String message) {}
});