Android 11 final release is here! 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.
- Handling content URI sharing
- Incremental permission requests
- Sensitive data access in the foreground
- Using resettable identifiers
Give proper URI permissions to other apps.
With the package visibility 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 implementation guide is available for common use cases. In general, an app must have visibility (verified using the
<a class="cm id jo jp jq jr" href="https://developer.android.com/reference/android/content/pm/PackageManager" target="_blank" rel="noopener nofollow noreferrer">PackageManager</a> 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_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.
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 URI patterns. If so, your content providers are already compatible on Android 11!
Request permissions incrementally.
This Android user study shows that users are more likely to grant a permission if the request aligns with their expectations. Therefore it is best practice to request permissions in-context, when a feature in your app needs those permissions.
Top user permission grant reasons. Source: Android user study.
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 policy restricting unnecessary background location access. To check where your app might be accessing background location, go through this checklist. 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.
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
<em class="kl">requestPermissions()</em> API call will be ignored as well.
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.
ActivityCompat or frameworks API is used:
Likewise, if the Jetpack Activity library is used:
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 foreground service changes.
You may declare the usage of multiple data types in the manifest.
If your implementation is based on a long-running worker 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 manifest file.
Include this element in your app’s manifest with proper foreground service types defined.
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.
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.
Since Android 10, 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
<a class="cm id jo jp jq jr" href="https://developer.android.com/reference/android/telephony/TelephonyManager#getSimSerialNumber()" target="_blank" rel="noopener nofollow noreferrer">getSimSerialNumber()</a> method. In Android 11, the system has further restricted access by applying similar restrictions to the
<a class="cm id jo jp jq jr" href="https://developer.android.com/reference/android/telephony/SubscriptionInfo#getIccId()" target="_blank" rel="noopener nofollow noreferrer">getIccId()</a> 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
<a class="cm id jo jp jq jr" href="https://developer.android.com/reference/android/telephony/SubscriptionInfo#getSubscriptionId()" target="_blank" rel="noopener nofollow noreferrer">getSubscriptionId()</a> 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. Learn more.
The platform and Google Play services offer a number of identifiers with various uniqueness, resettability, and scope that are suitable for various use cases. You can check out more identifier use cases.
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 related improvements in Android 11 and privacy best practices in the documentation.
Thanks to Kevin Hufnagle and Jeremy Walker.
By Fred Chung