Thursday, June 18, 2015

Android M Permissions and Forward Compatibility for Legacy Apps


Android M introduces a new runtime permissions model. Under this model, user is not presented with a list of permissions at install or update time. Instead, an app throws a pop-up at runtime when it needs a permission in order to complete a specific action (e.g. reading contacts, taking picture etc.). Once an app is installed on an Android M device, a user has the option to revoke permissions by navigating to the settings screen. Android M introduces a new Context.checkSelfPermission() API that an app targeted for M must use to check if it currently has the required permission to perform a specific operation. Also, on M, an app can request permissions at runtime by calling Activity.requestPermissions() API. But, what if you are still not prepared to support the new permissions model and the targetSdkVersion of your app is still API level 22 or lower? In another scenario, what if someone tries to install your legacy app on M developer preview and turns off its permissions? Will this break your app’s functionality and cause your app to crash?

In answer to the above described scenarios, your app will not crash and M will return an empty data set. For example, if you request to read Contacts Provider component, and the Contacts permission is revoked, you will get no results. In this blog post, I will demonstrate how Android M gracefully handles permission revocation for legacy apps to be forward compatible when a user turns off app permissions at runtime. 

Note: this post is based off release 1 of Android M Developer Preview. The way runtime permission revocation works is subject to change. 

An example

I created an example project on Github consisting of a single Activity that reads phone contacts. The focus here should not be the code that queries the ContactProvider to retrieves contacts, but to see how Android M reacts to turning off permission at runtime post install or update.

 

Adding permissions in AndroidManifest.xml

You still need to declare permissions in the Manifest. The following code snippet shows that the app claims READ_CONTACTS permission.

<uses-permission android:name="android.permission.READ_CONTACTS" />

Given that the targetSdkVersion of our app is 22 (Lollipop 5.1 release), all permissions claimed by the app shall be presented to the user at install time. It's worth noting that the READ_CONTACTS permission falls under the CONTACTS group. A permission group is a logical classification of related permissions. Screenshot 1 below shows the app running when Contacts permission is granted on an Android M Developer Preview release 1 Android Virtual Device (AVD) .

Screenshot 1: App displays all contacts when permission is granted

Revoking permissions

As mentioned earlier, on M, users can revoke an app's permissions at any time after the initial install or update. Screenshot 2 shows the permissions settings screen for our app. As you can see, currently, Contacts permission is enabled.

Screenshot 2: Granular app permission in settings

Screenshot 3 depicts a pop-up that user sees when revoking app permissions.

Screenshot 3: User can revoke any permission at runtime


Since the targetSdkVersion of our app is 22, when the user turns off permission after it is installed (or updated), Android M will throw a warning message indicating that the app may malfunction. If the user clicks on Deny, permission to read Contacts will be revoked, but the OS will not notify our app. That said, Android would handle permission revocation by returning no data when a legacy app calls a permission protected API. You will not get a SecurityException if a user turns off an app’s permission. In our contacts app, you will notice that after we turn off the Contacts permission, our app will continue to run but we will not get any contacts. The ContactsProvider will return an empty set. The cursor will have zero results. Screenshot 4 shows our contacts app when Contacts permission is turned off.

Screenshot 4: after revoking the Contacts permission, the dataset is empty


Caution: According to this Google I/O 2015 talk, when a user turns off an app’s permission while it is performing a certain restricted feature, M shall terminate the process.


Backward compatibility

If you install an app that uses the new permissions model on a device that is not running M, system will behave the old way, that is, the user will be prompted to grant all permissions declared in the Manifest file at install and update time only.


Conclusion

From a user experience standpoint, granular app permissions make sense. Users will be presented with a pop-up message the first time an app tries to use a protected API (for that action alone) or it can even request specific permissions, instead of presenting a bunch of permissions at install or update time. For example, when a user presses the microphone button, an app can request for the microphone permission. A user would always know the exact reason for the requested permission. Your legacy apps will continue to work even if a user turns off its permissions after install or update. But, your app may not behave properly because now you will get empty set or fake data. As developers, it is in our best interest to migrate to the new runtime permissions model introduced by Android M. Also, if an app targets the M Developer Preview, it must use the new permissions model. This means that in addition to declaring permissions in the Manifest, an app must also leverage the new APIs at runtime to check whether it has the required permission, and request permissions if the user has revoked them.

References