Runtime Permissions, ACTION_PICK, and Contacts

In my previous blog post, I wrote:

We are likely to stumble over these sorts of [corner] cases from time to time over the next year, as we collectively apply the runtime permissions system to our apps and see where things go haywire.

Stack Overflow user Venator85 ran into another case a few days ago.

If you use ACTION_PICK to pick a contact out of ContactsContract.Contacts, the Uri that you get back has read permissions automatically granted. While you cannot read data for arbitrary contacts, you can read data about this specific contact, by using query() on a ContentResolver (directly, via a CursorLoader, etc.). You do not need the READ_CONTACTS permission for this.

Personally, I haven’t been a fan of this behavior, simply because I doubt that users realize that picking a contact grants the requesting app access to all details about that contact.

Perhaps you had a similar concern, and so you requested the READ_CONTACTS permission in your manifest, “in the interests of full disclosure”. In general, this is a reasonable thing to do. However, it does trip over a problem with the Android 6.0 legacy app support for runtime permissions.

Because you requested READ_CONTACTS, the Contacts permission group will appear in the Permissions for your app in Settings. This is standard behavior.

If your targetSdkVersion is 23 or higher, everything is fine. Even if you do not use requestPermissions() to request READ_CONTACTS, you still have access to the details of the picked contact. This occurs regardless of whether or not the user has manually granted rights to the Contacts permission group in Settings. IOW, things work as expected.

However, if your targetSdkVersion is lower than 23 (e.g., 22), and your app is running on Android 6.0, and the user goes into Settings and revokes READ_CONTACTS, you can no longer access this data. You do not get a SecurityException, but instead you get an empty Cursor (no rows, no columns).

Why? It appears that you are getting legacy-app “app ops”-style behavior. Even though you don’t need the permission, because the user revoked the automatically-granted permission, all access to ContactsContract behaves as though the user has no contacts.

The workarounds are:

  • get rid of READ_CONTACTS, as you do not need it, or

  • upgrade your app to have targetSdkVersion 23 and deal with runtime permissions, or

  • detect the empty Cursor and deal with it accordingly

I filed an issue about this, though I am skeptical that it will be addressed.