Android 11 final release is ! This release builds upon the privacy improvements in previous releases, and provides even better control and transparency for users as well as guardrails to help apps handle data responsibly.

Many of these improvements reinforce modern best practices applicable to recent Android releases (they aren’t specific to Android 11!). In this article, we will examine 4 of these best practices to help you future-proof your design and plan for compatibility test cases.

  1. Handling content URI sharing
  2. Incremental permission requests
  3. Sensitive data access in the foreground
  4. Using resettable identifiers

 

Give proper URI permissions to other apps.

With the  change in Android 11, apps that target API level 30 will have limited visibility to other installed packages on the device by default. This is designed to provide better accountability for apps to “see” other packages on the device.

To ease migration, an  is available for common use cases. In general, an app must have visibility (verified using the  API) to other installed packages in order to interact with them. This applies to, for example, starting a service or reading from a content provider that belongs to another app.

Your content provider’s access model likely involves sending an implicit intent as opposed to an explicit intent targeted to a given package. As a result, your design cannot assume the receiving app’s target API level, which determines whether the app is subject to package visibility restrictions on Android 11.

To ensure that the receiving app has visibility to your package and thus, can access any shared URIs, you need to include the FLAG_GRANT_READ_URI_PERMISSION and/or FLAG_GRANT_WRITE_URI_PERMISSION URI flag in the intent. Note that the write permission does not imply read access. Upon getting triggered by the intent, the receiving app will be granted temporary access to the URIs.

val shareIntent = Intent(Intent.ACTION_VIEW).apply {
    flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
    data = // Content Uri to be shared with others
}

As you plan to update your app’s target SDK version (even to a version before Android 11), pay attention to cases that involve sharing content provider access with another app and make sure that proper URI permissions are granted. This is applicable regardless of the owner of the content provider.

Limiting the level of data access to what’s required by the task at hand is generally a good practice. Ideally, your content providers should already have proper sharing permissions for individual . If so, your content providers are already compatible on Android 11!

Request permissions incrementally.

This  shows that users are more likely to grant a permission if the request aligns with their expectations. Therefore it is best practice to  in-context, when a feature in your app needs those permissions.

Top user permission grant reasons. Source: .

Image for post

This is especially applicable to sensitive permissions like location access. Since Android 10, the platform has introduced a fine-grain location model which distinguishes foreground from background location access. Most location use cases only need foreground access, such as when the user is engaging with an activity.

In fact, Google Play has a  restricting unnecessary background location access. To check where your app might be accessing background location, . If your app requires background location, such as for a geofencing use case, make sure that it’s critical to your functionality.

For applicable apps, they should request foreground location at first, then background location at a later time. This approach gives users the option to control the level of permission grants. Additionally, you may strategically display an explanation or design an appropriate UX, to provide additional context on how the user may benefit from granting an additional location permission.

Image for post

Android 11 enforces incremental location permission requests for apps that target API level 30. Any permission requests that include both foreground location (either fine or coarse) and background location permissions will be ignored and result in the following error message.

E/GrantPermissionsActivity: Apps targeting 30 must have foreground permission before requesting background and must request background on its own.

Note that any other non-location permissions in the same requestPermissions() API call will be ignored as well.

As the requestPermissions API takes an array of permissions as an input parameter, you may have existing code that demonstrates the following patterns. You’re encouraged to audit and design an alternative user flow as necessary.

If ActivityCompat or frameworks API is used:

requestPermissions(
        arrayOf(
            // Do not request foreground and background location permissions together,
            // as they will be ignored along with any other permissions in the request. 
            // Request location permissions incrementally instead. 
            android.Manifest.permission.ACCESS_COARSE_LOCATION,         
            android.Manifest.permission.ACCESS_BACKGROUND_LOCATION,
            ...)
        )

Likewise, if the Jetpack Activity library is used:

// Using Activity library.
val requestPermissionsLauncher =
                registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
                    map: MutableMap<String, Boolean> ->
                        ...
                }

...
requestPermissionsLauncher.launch(
        arrayOf(
            // Do not request foreground and background location permissions together,
            // as they will be ignored along with any other permissions in the request. 
            // Request location permissions incrementally instead.
            android.Manifest.permission.ACCESS_COARSE_LOCATION,         
            android.Manifest.permission.ACCESS_BACKGROUND_LOCATION,
            ...)
    )

Proper access to location, microphone, and camera.

Android’s design encourages transparency when accessing sensitive data such as microphone, camera, and location. For example, apps can only use the microphone and camera while in the foreground, such as when the UI is visible to the user. This improves transparency, so users can make an informed decision on enabling related features.

If your app has existing foreground services that access sensitive data, be sure that the use case involves direct user interactions, where the user can control the task being performed. For example, in a video conferencing app, you may use a foreground service to support an active meeting session that involves microphone and camera access. There should also be an affordance for the user to start and stop the session, and thus the foreground service.

Additionally, your app must properly set the foregroundServiceType attribute to indicate usage of location, microphone, or camera. This gives the system visibility to apps that need the data and is a requirement for apps that target Android 11. Learn more about .

You may declare the usage of multiple data types in the manifest.

android:foregroundServiceType = "microphone | location | camera"

If your implementation is based on a  within WorkManager, it is in fact backed by a foreground service called SystemForegroundService. You should include the appropriate foreground service types in your app’s manifest, which will be merged with Jetpack library’s AAR .

Include this element in your app’s manifest with proper foreground service types defined.

<service
    android:name="androidx.work.impl.foreground.SystemForegroundService"
    android:foregroundServiceType="location|microphone"
    tools:node="merge" />

When you promote the worker to run as a foreground service, you will need to pass the appropriate foreground service types into the ForegroundInfo object. These types must be the same as those defined in the merged manifest above, or a subset of them.

setForeground(
    ForegroundInfo(NOTIF_ID,
                   notification.build(),
                   // Foreground service types.
                   ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION or ...)
)

Migrate off of non-resettable identifiers.

The Android system uses a number of non-resettable hardware identifiers, such as the IMEI, to support various OS functionality. The durability and uniqueness of these identifiers make them unsuitable for most identification use cases due to privacy considerations.

, the system has limited access to various non-resettable identifiers. For example, only privileged system apps with the READ_PRIVILEGED_PHONE_STATE permission can access SIM hardware identifiers via the  method. In Android 11, the system has further by applying similar restrictions to the  method, which now returns an empty string.

Apps that may previously be utilizing this identifier to link functionality to a certain SIM should verify compatibility with the “empty string” return value on Android 11. One alternative is to use the  method, which returns a 1-based unique index value for a given SIM on the device. That is, if the same SIM is reinstalled on a device, it will retain the previously assigned subscription identifier unless the device is factory-reset. .

The platform and Google Play services offer a number of  with various uniqueness, resettability, and scope that are suitable for various use cases. You can check out more identifier .

I hope you find these recommendations useful in helping you prepare for target API level update and making your app’s design more privacy-friendly! You can learn more about other  and  in the documentation.

Thanks to Kevin Hufnagle and Jeremy Walker.

By Fred Chung

Previous VMware Tanzu Support Community Honored With STAR Award For Innovation And Excellence
Next CNCF End User Technology Radar: Observability, September 2020