18

I'm studying the new permission model of Android Marshmallow but I'm facing an issue I find odd.

An app with targetSdkVersion 22 (so not yet using the new permission model of Android Marshmallow) declares the READ_CONTACTS permission in the manifest:

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

and tries to read the phone number of a contact via Intent.ACTION_PICK:

Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);
startActivityForResult(intent, PICK_CONTACT_REQUEST);

When running on a device with Marshmallow MRA58K, after I revoke the permission after installing the app via ADB, the ContextCompat.checkSelfPermission() method still returns PERMISSION_GRANTED, but the operation fails later when accessing the contacts because a cursor with no records is returned. As I understood, this is the default "retrocompatibility" strategy to avoid legacy app to crash.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == PICK_CONTACT_REQUEST && resultCode == Activity.RESULT_OK) {
        Log.i("", "Permission granted: " + (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED));
        Uri contactUri = data.getData();
        Cursor c = null;
        try {
            c = getContentResolver().query(contactUri, new String[]{ContactsContract.CommonDataKinds.Phone.NUMBER}, null, null, null);
            if (c != null && c.moveToFirst()) {
                Log.i("", "got phone number: " + c.getString(0));
            } else {
                Log.w("", "No data received");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (c != null) {
                c.close();
            }
        }
    } else {
        super.onActivityResult(requestCode, resultCode, data);
    }
}

How can I safely detect if the user explicitly before the permission before attempting the operation?

I also tried the official Google sample at https://github.com/googlesamples/android-RuntimePermissions setting the targetSdkVersion to 22 with the same result: it logs that the permission is granted even if the user revoked it, ant then the operation fails.

Thanks ;)

Venator85
  • 10,245
  • 7
  • 42
  • 57
  • 1
    "the ContextCompat.checkSelfPermission() method still returns PERMISSION_GRANTED" -- correct. `checkSelfPermission()` will return `PERMISSION_GRANTED` 100% of the time for apps with `targetSdkVersion` of 22 and lower. "How can I safely detect if the user explicitly before the permission before attempting the operation?" -- you can't in general. In your case, you could try to `query()` for something known to exist (e.g., name), and if you get zero results back, you know you have no access to the contact data. – CommonsWare Oct 09 '15 at 13:21
  • 1
    Bear in mind that if all you ever do is work with the picked contact right when the contact is picked, you do not need `READ_CONTACTS` at all, for any version of Android, as you are automatically granted access just for the picked contact. You might try just removing that permission entirely, as that would prevent the user from revoking it. Your case brings up some interesting effects; I'll try to run some related experiments next week. – CommonsWare Oct 09 '15 at 13:23
  • Hmm, this seems a terrible API design choice to me. So is there no other way to detect a revoked permission before attempting the operation? Maybe using AppOps? – Venator85 Oct 09 '15 at 13:29
  • 4
    "So is there no other way to detect a revoked permission before attempting the operation?" -- sure. Raise your `targetSdkVersion` to 23 and add the runtime permission support. By having a `targetSdkVersion` of 22 or below, you are explicitly saying that you *don't* want to deal with runtime permissions. – CommonsWare Oct 09 '15 at 13:31
  • FWIW, I wrote up [a blog post](https://commonsware.com/blog/2015/10/12/runtime-permissions-action-pick-contacts.html) about this, and I filed [an issue](https://code.google.com/p/android/issues/detail?id=189670) regarding the regression. – CommonsWare Oct 12 '15 at 19:41

2 Answers2

10

Use the android.support.v4.content.PermissionChecker

https://developer.android.com/reference/android/support/v4/content/PermissionChecker.html

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
hans
  • 124
  • 4
  • 1
    Works like a charm! Thanks – Venator85 Oct 21 '15 at 12:11
  • My use case is a little different (camera), but here's what I found: replacing `ContextCompat.checkSelfPermission` with `PermissionsChecker.checkSelfPermission` helps checking the if you really have the permission. The problem is that if you're triggering `requestPermissions` based on this check and the user clicks on *Deny*, `onRequestPermissionsResult` will still receive a `PERMISSION_GRANTED` in the `grantResults` array. – TWiStErRob Apr 21 '16 at 15:19
  • @TWiStErRob in that case, theoretically, you could just check again using `PermissionChecker` that the requested permissions are granted. But, if you use `targetSDK` 22 (or lower), in case the user grants the permission, it will kill the app process and your callback will not be called. So, the only way to properly handle this is to upgrade to `targetSDK` 23 – Muzikant Jun 23 '16 at 10:14
  • Can anyone tell me , in which version of support library `PermissionChecker` class is introduced ? – Akhilesh Kumar Sep 28 '16 at 10:04
  • @AkhileshKumar 23 guy – Hien Nguyen May 09 '17 at 04:13
  • Hi, guys. My targetSdk is 26. PermissionChecker returns the 0 code(GRANTED), but i denied the permission. So, maybe somebody resolved it. Let me know. – Taras Vovkovych Mar 11 '18 at 10:03
0

In order to use the new permission models (and checking if your app has or not the permission) you need to update your target SDK to 23. It doesn't make any sense to keeping target 22.

greywolf82
  • 21,813
  • 18
  • 54
  • 108