Android Q gives users more control over apps' access to device location.

When an app running on Android Q requests location access, users have three options:

  1. All the time (foreground and background)
  2. While in use (foreground only)
  3. Deny

This codelab will walk you through best practices for dealing with location in Android Q.

What you will build

You will modify a pre-existing location app to support Android Q.

If you would like to learn how to build a similar app from the ground up, check out our other codelab.

There are two parts to this codelab:

  1. Add support for foreground-only location, or while-in-use access.
  2. Add support for foreground and background location, or all-the-time access.

The app has a simple UI:

Prerequisites

Familiarity with Android development and some familiarity with location services.

What you'll learn

What you'll need

Concepts and setup

In this codelab, you'll learn how to quickly support location in Android Q. At the end of the codelab, you can expect to have an app that supports foreground-only and foreground and background location (following best practices for while-in-use and all-the-time location access).

Concepts

The focus of this codelab is to show you what is different in Android Q and how you can support it. Most of the code related to actually tracking location is already completed for you, as this hasn't changed much. If you would like to learn more about building a location app from scratch, see our documentation.

That said, while we do expect a basic understanding of location in Android, it doesn't hurt to review the basics here.

Basics

Android gives your applications access to the location services supported by the device through classes in the android.location package. The central component of the location framework is the LocationManager system service, which provides APIs to determine location and bearing of the underlying device (if available).

In order to receive location updates from NETWORK_PROVIDER or GPS_PROVIDER, you must request the user's permission by declaring either the ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission, respectively, in your Android manifest file. Without these permissions, your application will fail at runtime when requesting location updates.

Android Q augments this by allowing users to choose when you have access to this information ("while in use", "all the time", "not at all").

To support the additional location control, Android Q introduces a new location permission, ACCESS_BACKGROUND_LOCATION.

Unlike the existing ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions, the new permission only affects an app's access to location when it's running in the background. An app is considered to be in the background unless one of its activities is visible or the app is running a foreground service.

If your app has a foreground service (along with a notification), then you don't have to request background location access, but you will still need to declare that your foreground service has a foreground service type of "location".

Now that you have a basic idea of what we are doing, let's get started with the code!

Clone the starter project repo

To get you started as quickly as possible, we have prepared a starter project for you to build on. If you have git installed, you can simply run the command below. (You can check by typing git --version in the terminal / command line and verify it executes correctly.)

 git clone https://github.com/googlecodelabs/while-in-use-location

If you do not have git you can get the project as a zip file:

Download Zip

Import the project

Start Android Studio, and select "Open an existing Android Studio project" from the Welcome screen, open the project directory and double click on the build.gradle file in the while-in-use-location directory:

Click OK on "Import Project from Gradle" screen without making any changes.

After the project has loaded, you may also see an alert that Git isn't tracking all your local changes, you can click "Ignore" or the "X" in the upper right. (You won't be pushing any changes back to the Git repo.)

In the upper-left corner of the project window, you should see something like the image below if you are in the Android view. (If you are in the Project view, you will need to expand the project to see the same thing.)

There are two folder icons (base and complete). Each of them are known as a "module".

Please note that Android Studio might take several seconds to compile the project in the background for the first time. During this time you will see a spinner in the status bar at the bottom of Android Studio:

We recommend that you wait until this has finished before making code changes. This will allow Android Studio to pull in all the necessary components.

In addition, if you get a prompt saying "Reload for language changes to take effect?" or something similar, select "Yes".

Understand the starter project

All right, you're set up and ready to support location in Android Q. We'll be using the base module, which is the starting point for adding foreground-only or "while-in-use" location support. In other words, you'll add code from each step to base.

The complete module can be used for checking your work, or for you to reference if you encounter any issues.

Overview of key components:

Emulator setup

If you need help setting up an Android emulator, refer to the Run your app article.

Run the starter project

Let's run our app.


Summary

In this step you've learned about:

Next up

Let's start with the foreground-only location use case, or "while-in-use" access.

Code step 2

In this section, you'll target Android Q and add support for foreground-only location tracking, or "while-in-use" access.

What does foreground only mean? It means that the app is tracking location while it is in the foreground. That means either an activity is in the foreground or that the app has a service running in the foreground (with a notification).

Our app already has a feature that only tracks the user in the foreground using an activity or service running in the foreground, so there isn't a lot of work to do. Your app might be similar, as this is a best practice for getting frequent location updates.

In fact, all you have to do is specify that your foreground service is used for location purposes.

Let's add support for this now.

Target Q

In the base module, search for the TODO: Step 2.1, Target Q in the the build.gradle file.

Make these changes:

  1. compileSdkVersion to 'android-Q' (with the quotation marks)
  2. buildToolsVersion to "29.0.0 rc1" (with the quotation marks)
  3. targetSdkVersion to 'Q' (with the quotation marks).

Your code should now look something like this:

android {
   // TODO: Step 2.1, Target Q.
   compileSdkVersion 'android-Q'
   buildToolsVersion "29.0.0 rc1"
   defaultConfig {
       applicationId "com.example.android.whileinuselocation"
       minSdkVersion 26
       targetSdkVersion 'Q'
       versionCode 1
       versionName "1.0"
   }
...
}

After you do this, you will be asked to sync your project. Click on Sync Now.

After that, your app is ready for Android Q.

Add Foreground Service Type

In Android Q, you are required to include the type of your foreground service. In our case, it is being used to track location.

In the base module, search for TODO: 2.2, Add foreground service type in the AndroidManifest.xml and add the code below to the <service> element:

android:foregroundServiceType="location"

Your code should now look something like this:

<application>
   ...

   <!-- Foreground services in Q+ require type. -->
   <!-- TODO: 2.2, Add foreground service type. -->
   <service
       android:name="com.example.android.whileinuselocation.ForegroundOnlyLocationService"
       android:enabled="true"
       android:exported="false"
       android:foregroundServiceType="location" />
</application>

That's it! Your app now supports Android Q location for "while-in-use" by just following the best practices in location for Android.

Run app

Run your app from Android Studio and try the foreground only location button.

Everything should work as it did before, but now it works on Android Q. If you didn't accept the permissions for locations before, you should now see the new permission screen!

Summary

In this step you've learned:

Next up

Let's now look at an example where you want access to the device's location all the time.

Code step 3

In this section, you'll add support for retrieving location from the background in Android Q. You should generally prefer foreground-only location support, but there are some use cases where you do need location in the background.

Add the background permission in the manifest

Android Q includes a new permission to access location in background that you must add to your manifest.

In the base module, search for TODO: 3.1, Add background uses-permission to manifest in the AndroidManifest.

Add the new <uses-permission> element below the comment:

<!-- TODO: 3.1, Add background uses-permission to manifest. -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

You are now allowed to ask for all-the-time access to device location, including in the background. Let's add the checks and requests for that permission now.

Review code check for Android Q

In the base module, search for Step 3.2, review code checks for devices with Q in the MainActivity.kt. and review the line below.

// TODO: Step 3.2, review code checks for devices with Q.
private val runningQOrLater = BuildCompat.isAtLeastQ()

We'll be adding code to check and request the new background location permission. However, this code is not required unless you are running on Android Q or higher.

As called out in the comments, you would usually check for the Android version with this code:

Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q

However, because Android Q hasn't been released to production yet, we need to use isAtLeastQ().

Now you have a way to include these new checks/requests in Android Q only. Let's get started with adding some code.

Add check for access to background location

In the MainActivity.kt, search for TODO: Step 3.3, Add permission check for background permission and replace the line directly below the comment with this code:

// TODO: Step 3.3, Add check for background permission.
val backgroundPermissionApproved =
   if (runningQOrLater) {
       ActivityCompat.checkSelfPermission(
           this, Manifest.permission.ACCESS_BACKGROUND_LOCATION
       ) == PackageManager.PERMISSION_GRANTED
   } else {
       true
   }

If you are used to checking permissions in Android, this should look very familiar. The only difference is you are now checking the ACCESS_BACKGROUND_LOCATION permission.

We are using some Kotlin magic to do the following:

  1. Check if we are running Q or not (from our last step)
  2. If we are, check the permission as usual
  3. If we are not, then we return true, as there isn't a permission for background location in Android prior to Q.

You can see in the following line (already included in project), that we simply use the && operator on the foreground (ACCESS_FINE_LOCATION) and the background (ACCESS_BACKGROUND_LOCATION) location permissions to determine whether an app has all-the-time access to device location.

Now we know whether the user has approved accessing location in the background, but we still need to allow the user to actually approve access.

Add request background location access

In the MainActivity.kt, search for TODO: Step 3.4, Add another entry to permission request array and add the code below:

// TODO: Step 3.4, Add another entry to permission request array.
if (runningQOrLater) {
   permissionRequests.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}

If you look at the full block now, it should look like this:

val permissionRequests = arrayListOf(Manifest.permission.ACCESS_FINE_LOCATION)
// TODO: Step 3.4, Add another entry to permission request array.
if (runningQOrLater) {
   permissionRequests.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}

You can see from the first line, we are initializing an ArrayList to pass to ActivityCompat.requestPermissions().

In our new code, we check whether we are running on Android Q, and if we are, we simply add another entry to the ArrayList for access to the location in the background.

This will trigger the user to choose the level of access they want: either while-in-use access (foreground location permission only) or all-the-time access (foreground and background location permissions).

One other note, ActivityCompat.requestPermissions() doesn't actually take an ArrayList, so we need to convert it to an actual Array with toTypedArray().

Check the background location was approved in onRequestPermissionsResult()

We're almost there!

In the MainActivity.kt again, search for TODO: Step 3.5, For Q, check if background permissions approved in request code and add this block of code:

// TODO: Step 3.5, For Q, check if background permissions approved in request code.
if(runningQOrLater) {
   foregroundAndBackgroundLocationApproved =
       foregroundAndBackgroundLocationApproved &&
               (grantResults[1] == PackageManager.PERMISSION_GRANTED)
}


Here we are checking if we are running on Android Q. If we are, we check that the second permission was approved. If you recall from our previous step, the second item in our array was ACCESS_BACKGROUND_LOCATION.

Since we are already checking the first result in our code for Android 9 or lower, we can piggyback on that by just reusing the same variable, foregroundAndBackgroundLocationApproved, and && it with our new permission. After all, we can't say it was approved unless both the permissions were granted.

The full code should look like this:

var foregroundAndBackgroundLocationApproved =
   grantResults[0] == PackageManager.PERMISSION_GRANTED

// TODO: Step 3.5, For Q, check if background permissions approved in request code.
if(runningQOrLater) {
   foregroundAndBackgroundLocationApproved =
       foregroundAndBackgroundLocationApproved &&
               (grantResults[1] == PackageManager.PERMISSION_GRANTED)
}

Review call to location in foreground and background

No code to add in this step. We just want to review one more line.

Search for TODO: Step 3.6, review method call for foreground and background location which is just a couple lines below the code you just added.

This is just the call to start tracking location after we've verified that all permissions were granted.

// TODO: Step 3.6, review method call for foreground and background location.
foregroundAndBackgroundLocationApproved ->
   startForegroundAndBackgroundLocation()

OK, you are done!

Now let's try out our app. Remember, if you got lost along the way, have a look at the complete module. That has all the completed/working code.

Run app

Your app now fully supports Android Q location. Let's try it out!

Run your app from Android Studio. Now, when you click on access location in the foreground and background you should see one of the following screens, depending on which permissions are approved:

You can see after the permissions are granted, your app starts showing updates.

Summary

In this step you've learned about:

Next up

Now that you have supported both foreground only and foreground + background location, let's discuss best practices.

By checking and requesting location permissions in the ways shown in this guide, your app can successfully keep track of its access level regarding the device's location.

For more information about how to keep your users' data safe, see the permissions best practices guide.

Ask only for the permissions you need

Ask for permissions only when needed. For example:

Support graceful degradation if permission isn't granted

To maintain a good user experience, design your app so that it can gracefully handle the following situations:

Summary

In this step you've learned:

Check these links for more information: