Getting started with the Driver SDK for Android

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.

Build and dependencies 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:

  1. 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.

  2. 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.

  3. 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:

  1. 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>
    
  2. Driver SDK depends on Navigation SDK, this dependency is configured in such a way that if a specific version of Navigation SDK is needed, it needs to be explicitly defined in the build configuration file like the following, omitting the mentioned code block will enable the project to always download the latest version of the Navigation SDK within the major release version. Note that the combined behaviors of the latest versions of Driver SDK and Navigation SDK have underwent rigorous testing before their releases.

    Arrange the dependency configuration of your development and release environments accordingly.

    Gradle

    Add the following to your build.gradle:

    dependencies {
      ...
      implementation 'com.google.android.libraries.navigation:navigation:5.0.0'
    }
    

    Maven

    Add the following to your pom.xml:

    <dependencies>
      ...
      <dependency>
        <groupId>com.google.android.libraries.navigation</groupId>
        <artifactId>navigation</artifactId>
        <version>5.0.0</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:

  1. Open your root-level build.gradle file and add the following code to the dependencies element under buildscript.

    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")
        }
    }
    
  2. Open your app-level build.gradle file and add the following code to the plugins element.

    Groovy

    id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
    

    Kotlin

    id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
    
  3. If you use Android Studio, sync your project with Gradle.

  4. Open the local.properties in your project level directory, and then add the following code. Replace YOUR_API_KEY with your API key.

    MAPS_API_KEY=YOUR_API_KEY
    
  5. In your AndroidManifest.xml file, go to com.google.android.geo.API_KEY and update the android: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.

The licenses information can be found at the "third_party_licenses.txt" file in the unarchived AAR file.

Refer to https://developers.google.com/android/guides/opensource on how to include open source notices.

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:

  1. Obtain a Navigator object from the NavigationApi.

    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
        }
      },
    )
    
  2. 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()
    
  3. Use the DriverContext object to initialize the *DriverApi.

    Java

    RidesharingDriverApi ridesharingDriverApi = RidesharingDriverApi.createInstance(driverContext);
    

    Kotlin

    val ridesharingDriverApi = RidesharingDriverApi.createInstance(driverContext)
    
  4. Obtain the RidesharingVehicleReporter from the API object. (*VehicleReporter extends NavigationVehicleReporter.)

    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.