You can use the Driver SDK to provide enhanced navigation and tracking to your Trip and Order Progress application. The Driver SDK provides vehicle location and task updates to the On-demand Rides and Deliveries Solution Fleet Engine.
The Driver SDK keeps the Fleet Engine services and your custom services aware
of the vehicle’s location and state. For example, the vehicle can be ONLINE
or OFFLINE
, and the vehicle location changes as a trip progresses.
Minimum system requirements
The mobile device must be running Android 6.0 (API level 23) or later.
Maven configuration
Driver SDK versions 4.99 and later are available from the Google Maven repository.
Gradle
Add the following to your build.gradle
file:
repositories {
...
google()
}
Maven
Add the following to your pom.xml
file:
<project>
...
<repositories>
<repository>
<id>google-maven-repository</id>
<url>https://maven.google.com</url>
</repository>
</repositories>
...
</project>
Project Configuration
To use the Driver SDK, your app must target
minSdkVersion
23 or higher.
To run an app built with the Driver SDK, the Android device must have Google Play services installed.
Set up your development project
To set up your development project and get an API key for the project on the Google Cloud Console:
Create a new Google Cloud Console project, or select an existing project, for use with the Driver SDK. Wait a few minutes until the new project is visible on the Google Cloud Console.
In order to run the demo app, your project must have access to the Maps SDK for Android. In the Google Cloud Console, select APIs & Services > Library, then search for and enable the Maps SDK for Android.
Get an API key for the project by selecting APIs & Services > Credentials > Create credentials > API key. For more information about getting an API key, see Get an API key.
Add the Driver SDK to your app
The Driver SDK is available from the Google Maven repository. The repository includes the SDK's Project Object Model (.pom) files and Javadocs. To add the Driver SDK to your app:
Add the following dependency to your Gradle or Maven configuration, substituting the
VERSION_NUMBER
placeholder for the desired version of the Driver SDK.Gradle
Add the following to your
build.gradle
:dependencies { ... implementation 'com.google.android.libraries.mapsplatform.transportation:transportation-driver:VERSION_NUMBER' }
Maven
Add the following to your
pom.xml
:<dependencies> ... <dependency> <groupId>com.google.android.libraries.mapsplatform.transportation</groupId> <artifactId>transportation-driver</artifactId> <version>VERSION_NUMBER</version> </dependency> </dependencies>
Add the API key to your app
Once you have added the Driver SDK to your app, add the API key to your app. You must use the project API key you obtained when you set up your development project.
This section describes how to store your API key so that it can be more securely
referenced by your app. You should not check your API key into your version
control system. It should be stored in the local.properties
file, which is
located in the root directory of your project. For more information about the
local.properties
file, see
Gradle properties files.
To streamline this task, you can use the Secrets Gradle Plugin for Android.
To install the plugin and store your API key:
Open your root-level
build.gradle
file and add the following code to thedependencies
element underbuildscript
.Groovy
buildscript { dependencies { // ... classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.0" } }
Kotlin
buildscript { dependencies { // ... classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.0") } }
Open your app-level
build.gradle
file and add the following code to theplugins
element.Groovy
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
Kotlin
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
If you use Android Studio, sync your project with Gradle.
Open the
local.properties
in your project level directory, and then add the following code. ReplaceYOUR_API_KEY
with your API key.MAPS_API_KEY=YOUR_API_KEY
In your
AndroidManifest.xml
file, go tocom.google.android.geo.API_KEY
and update theandroid:value
attribute as follows:<meta-data android:name="com.google.android.geo.API_KEY" android:value="${MAPS_API_KEY}" />
The following example shows a complete manifest for a sample app:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.driverapidemo">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/_AppTheme">
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${MAPS_API_KEY}" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Include the required attributions in your app
If you use the Driver SDK in your app, you must include attribution text and open source licenses as part of your app's legal notices section. It's best to include the attributions as an independent menu item or as part of an About menu item.
You can find the required attribution text and open source licenses in the Driver SDK zip file:
NOTICE.txt
LICENSES.txt
Dependencies
If you use ProGuard to optimize your builds, you may need to add the following lines to your ProGuard configuration file:
-dontwarn com.google.**
-dontwarn okio.**
The minimum API level supported is 23.
Initializing the SDK
A provider ID (usually the Google Cloud Project ID) is required in order to
initialize the DriverContext
object. For more detail about setting up the
Google Cloud Project, see
Authentication and Authorization.
Before using the Driver SDK, you must first initialize the Navigation SDK. To initialize the SDK:
Obtain a
Navigator
object from theNavigationApi
.Java
NavigationApi.getNavigator( this, // Activity new NavigationApi.NavigatorListener() { @Override public void onNavigatorReady(Navigator navigator) { // Keep a reference to the Navigator (used to configure and start nav) this.navigator = navigator; } } );
Kotlin
NavigationApi.getNavigator( this, // Activity object : NavigatorListener() { override fun onNavigatorReady(navigator: Navigator) { // Keep a reference to the Navigator (used to configure and start nav) this@myActivity.navigator = navigator } }, )
Create a
DriverContext
object, populating the required fields.Java
DriverContext driverContext = DriverContext.builder(application) .setProviderId(providerId) .setVehicleId(vehicleId) .setAuthTokenFactory(authTokenFactory) .setNavigator(navigator) .setRoadSnappedLocationProvider( NavigationApi.getRoadSnappedLocationProvider(application)) .build();
Kotlin
val driverContext = DriverContext.builder(application) .setProviderId(providerId) .setVehicleId(vehicleId) .setAuthTokenFactory(authTokenFactory) .setNavigator(navigator) .setRoadSnappedLocationProvider(NavigationApi.getRoadSnappedLocationProvider(application)) .build()
Use the
DriverContext
object to initialize the*DriverApi
.Java
RidesharingDriverApi ridesharingDriverApi = RidesharingDriverApi.createInstance(driverContext);
Kotlin
val ridesharingDriverApi = RidesharingDriverApi.createInstance(driverContext)
Obtain the
RidesharingVehicleReporter
from the API object. (*VehicleReporter
extendsNavigationVehicleReporter
.)Java
RidesharingVehicleReporter vehicleReporter = ridesharingDriverApi.getRidesharingVehicleReporter();
Kotlin
val vehicleReporter = ridesharingDriverApi.getRidesharingVehicleReporter()
Authenticating with AuthTokenFactory
When the Driver SDK generates location updates,
it must send these updates to
the Fleet Engine server. In order to authenticate these requests, the
Driver SDK will call out to a caller-provided
instance of AuthTokenFactory
.
The factory is responsible for generating authentication tokens at location
update time.
How exactly tokens are generated will be specific to each developer's situation. However, the implementation will probably need to:
- fetch an authentication token, possibly in JSON format, from an HTTPS server
- parse and cache the token
- refresh the token when it expires
For details of the tokens expected by the Fleet Engine server, see Creating a JSON Web Token (JWT) for authorization.
Here is a skeleton implementation of an AuthTokenFactory
:
Java
class JsonAuthTokenFactory implements AuthTokenFactory {
private String token; // initially null
private long expiryTimeMs = 0;
// This method is called on a thread whose only responsibility is to send
// location updates. Blocking is OK, but just know that no location updates
// can occur until this method returns.
@Override
public String getToken(AuthTokenContext authTokenContext) {
if (System.currentTimeMillis() > expiryTimeMs) {
// The token has expired, go get a new one.
fetchNewToken(authTokenContext.getVehicleId());
}
return token;
}
private void fetchNewToken(String vehicleId) {
String url =
new Uri.Builder()
.scheme("https")
.authority("yourauthserver.example")
.appendPath("token")
.appendQueryParameter("vehicleId", vehicleId)
.build()
.toString();
try (Reader r = new InputStreamReader(new URL(url).openStream())) {
com.google.gson.JsonObject obj
= com.google.gson.JsonParser.parseReader(r).getAsJsonObject();
token = obj.get("Token").getAsString();
expiryTimeMs = obj.get("TokenExpiryMs").getAsLong();
// The expiry time could be an hour from now, but just to try and avoid
// passing expired tokens, we subtract 10 minutes from that time.
expiryTimeMs -= 10 * 60 * 1000;
} catch (IOException e) {
// It's OK to throw exceptions here. The StatusListener you passed to
// create the DriverContext class will be notified and passed along the failed
// update warning.
throw new RuntimeException("Could not get auth token", e);
}
}
}
Kotlin
class JsonAuthTokenFactory : AuthTokenFactory() {
private var token: String = ""
private var expiryTimeMs: Long = 0
// This method is called on a thread whose only responsibility is to send
// location updates. Blocking is OK, but just know that no location updates
// can occur until this method returns.
override fun getToken(context: AuthTokenContext): String {
if (System.currentTimeMillis() > expiryTimeMs) {
// The token has expired, go get a new one.
fetchNewToken(authTokenContext.getVehicleId())
}
return token
}
fun fetchNewToken(vehicleId: String) {
val url =
Uri.Builder()
.scheme("https")
.authority("yourauthserver.example")
.appendPath("token")
.appendQueryParameter("vehicleId", vehicleId)
.build()
.toString()
try {
val reader = InputStreamReader(URL(url).openStream())
reader.use {
val obj = com.google.gson.JsonParser.parseReader(r).getAsJsonObject()
token = obj.get("ServiceToken").getAsString()
expiryTimeMs = obj.get("TokenExpiryMs").getAsLong()
// The expiry time could be an hour from now, but just to try and avoid
// passing expired tokens, we subtract 10 minutes from that time.
expiryTimeMs -= 10 * 60 * 1000
}
} catch (e: IOException) {
// It's OK to throw exceptions here. The StatusListener you passed to
// create the DriverContext class will be notified and passed along the failed
// update warning.
throw RuntimeException("Could not get auth token", e)
}
}
}
This particular implementation uses the built-in Java HTTP client to fetch a token in JSON format from the developer's authentication server. The token is saved for reuse. The token is re-fetched if the old token is within 10 minutes of its expiry time.
Your implementation may do things differently, such as using a background thread to refresh tokens.
Exceptions in AuthTokenFactory
will be treated as transient unless they happen
repeatedly. After a number of attempts, the Driver SDK
will assume that the
error is permanent and will stop trying to send updates.
Status and Error Reporting with StatusListener
Since the Driver SDK performs actions in the
background, use the StatusListener
to trigger notifications when certain
events occur, such as errors, warnings, or debug messages. Errors may be
transient in nature (such as BACKEND_CONNECTIVITY_ERROR
), or they may
cause location updates to be stopped permanently (such as VEHICLE_NOT_FOUND
,
indicating a configuration error).
You provide an optional StatusListener
implementation like the following:
Java
class MyStatusListener implements StatusListener {
/** Called when background status is updated, during actions such as location reporting. */
@Override
public void updateStatus(
StatusLevel statusLevel, StatusCode statusCode, String statusMsg) {
// Status handling stuff goes here.
// StatusLevel may be DEBUG, INFO, WARNING, or ERROR.
// StatusCode may be DEFAULT, UNKNOWN_ERROR, VEHICLE_NOT_FOUND,
// BACKEND_CONNECTIVITY_ERROR, or PERMISSION_DENIED.
}
}
Kotlin
class MyStatusListener : StatusListener() {
/** Called when background status is updated, during actions such as location reporting. */
override fun updateStatus(statusLevel: StatusLevel, statusCode: StatusCode, statusMsg: String) {
// Status handling stuff goes here.
// StatusLevel may be DEBUG, INFO, WARNING, or ERROR.
// StatusCode may be DEFAULT, UNKNOWN_ERROR, VEHICLE_NOT_FOUND,
// BACKEND_CONNECTIVITY_ERROR, or PERMISSION_DENIED.
}
}
Notes on SSL/TLS
Internally, the Driver SDK implementation uses
SSL/TLS to communicate securely
with the Fleet Engine server. Older versions of Android (API versions 19 or
lower) may require a SecurityProvider
patch to be able to communicate with the
server. You should see this
article
for more information about working with SSL in Android. The article also
contains code samples for patching the security provider.
Enabling location updates
Once you have a *VehicleReporter
instance, enabling location updates is
straightforward:
Java
RidesharingVehicleReporter reporter = ...;
reporter.enableLocationTracking();
Kotlin
val reporter = ...
reporter.enableLocationTracking()
Location updates are sent at a regular interval when the vehicle state is
ONLINE
. Note that calling reporter.enableLocationTracking()
does not
automatically set the vehicle state to ONLINE
. You must
set the vehicle state explicitly.
By default, the reporting interval is 10 seconds. The reporting interval can
be changed with reporter.setLocationReportingInterval(long, TimeUnit)
. The
minimum supported update interval is 5 seconds. More frequent updates may
result in slower requests and errors.
Disabling location updates
When the driver's shift is finished, location updates can be stopped and the
vehicle marked offline by calling
DeliveryVehicleReporter.disableLocationTracking
or
RidesharingVehicleReporter.disableLocationTracking
.
This call will cause one final update to be scheduled for immediate delivery, indicating that the vehicle is offline. This update will not contain the user's location.
Setting the vehicle state
When location updates are enabled, setting the vehicle state to ONLINE
will
make the vehicle available for SearchVehicles
queries; similarly, marking a
vehicle as OFFLINE
will mark the vehicle as unavailable.
You have the option of setting the vehicle state on the server side (see Update a Vehicle), or directly in the Driver SDK:
Java
RidesharingVehicleReporter reporter = ...;
reporter.enableLocationTracking();
reporter.setVehicleState(VehicleState.ONLINE);
Kotlin
val reporter = ...
reporter.enableLocationTracking()
reporter.setVehicleState(VehicleState.ONLINE)
When location updates are enabled, a call to setVehicleState
will propagate at
the next location update.
Marking a vehicle as ONLINE
when location tracking is not enabled will result
in an IllegalStateException
. A vehicle can be marked as OFFLINE
when
location tracking is not yet enabled or explicitly disabled. This will result
in an immediate update. A call to
RidesharingVehicleReporter.disableLocationTracking()
will
set the vehicle state to OFFLINE
.
Note that setVehicleState
returns immediately, and updates are done on the
location update thread. Similar to error handling of locations updates, errors
updating the vehicle state are propagated using the optionally provided
StatusListener
set in the DriverContext
.