Android Foreground Service

Ferrostar provides an Android foreground service that runs automatically during a trip. Such a service is required to comply with Google's Foreground Location Requirements.

Overview

The Kotlin ForegroundServiceManager links FerrostarCore to the foreground service FerrostarForegroundService using a binder. This technique allows us to bind the foreground service to a trip operated by the core. When navigation starts, the foreground service is started and bound automatically. Similarly, when we stop navigating, the core will stop the foreground and unbind it.

Note: you must call stop on Ferrostar core to stop the notification. It does not automatically close out when the user arrives because location updates are not stopped.

Setting Up Permissions

This feature requires adding the following to your app or module AndroidManifest.xml.

    <!-- Foreground service permission -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />

    <application>
        <service
            android:name="com.stadiamaps.ferrostar.core.service.FerrostarForegroundService"
            android:foregroundServiceType="location"
            />
    </application>

Your app must still request POST_NOTIFICATIONS and FOREGROUND_SERVICE_LOCATION permissions on API 34+ (Upside Down Cake). See the demo app's call to request these permissions in your composable app.

  val allPermissions =
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
        arrayOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.POST_NOTIFICATIONS,
            Manifest.permission.FOREGROUND_SERVICE_LOCATION)
      } else {
        arrayOf(
            Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)
      }

  val permissionsLauncher =
      rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
        // TODO: Handle permission fialures.
      }

Using the Service Manager with FerrostarCore

In your app module, you'll need to pass in the ForegroundServiceManager as a parameter to construct FerrostarCore. Here's how to do that with the included notification builder.

val foregroundServiceManager: ForegroundServiceManager = FerrostarForegroundServiceManager(appContext, DefaultForegroundNotificationBuilder(appContext))

val core =
        FerrostarCore(
            ...,
            foregroundServiceManager,
            ...)

The demo app shows this using a lazy initializer.

TODO: link the dependency injection example here once we have a doc for that.

Customization

You can provide your own implementation of the ForegroundNotificationBuilder to customize the notification the foreground service publishes to users. To accomplish this, simply create a new Notification like the DefaultForegroundNotificationBuilder that implements the abstract ForegroundNotificationBuilder. Your class needs to create and build the foreground notification. This can include setting any required pending intents (e.g. openPendingIntent), portraying relevant information about the service to the user and so on.

class MyForegroundNotificationBuilder(
    context: Context
) : ForegroundNotificationBuilder(context) {

  override fun build(tripState: TripState?): Notification {
    if (channelId == null) {
      throw IllegalStateException("channelId must be set before building the notification.")
    }

    // Generate the notification builder. Note that channelId is set on newer versions of Android.
    // The channel is used to associate the notification in settings with the channel's title. This allows
    // a user to better understand the notification they're being presented in the android settings app.
    val builder: Notification.Builder =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
          Notification.Builder(context, channelId)
        } else {
          Notification.Builder(context)
        }

    // TODO: Build your notification off of the TripState here.


    // Set the open app pending intent. This example shows the case where the user tapping the notification will
    // open the app.
    builder.setContentIntent(openPendingIntent)

    // You can also use the provided `stopPendingIntent` which like Google Maps, Mapbox and others allows the user
    // to directly stop the navigation trip (and location updates) from the notification.

    return builder.build()
  }
}

When initializing the manager, all you need to do is change out the notification builder to your custom one.

val foregroundServiceManager: ForegroundServiceManager = FerrostarForegroundServiceManager(appContext, MyForegroundNotificationBuilder(appContext))

Learn More