15

I have AddActivity, which lets you get the URI from either a picture you can take from the camera, or an image that you can select from the gallery. Then you can go to DetailsActivity to view the image. I have it working right now until you restart the device. After you restart and try to go to DetailsActivity for that image, this is the error:

Caused by: java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{3a5e86d 2915:jeremy.com.wineofmine/u0a321} (pid=2915, uid=10321) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs

I went to the "Open Files Using Storage Access Framework" Android Development page and read up on the Persist Permissions section. I'm having trouble applying it to my project though.

I think the main thing I don't understand is that it looks like you need to call an intent (in my case inside the DetailsActivity), but I don't even have an intent there.

Here is the intent that lets you pick the gallery image. This is in AddActivity:

Intent intentGallery = new Intent(Intent.ACTION_OPEN_DOCUMENT);
            intentGallery.addCategory(Intent.CATEGORY_OPENABLE);
            intentGallery.setType("image/*");
            intentGallery.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intentGallery.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            startActivityForResult(intentGallery, SELECT_IMAGE);

In the DetailsActivity, this is where it actually crashes:

imageURI = Uri.parse(cursor.getString(cursor.getColumnIndexOrThrow(WineContract.WineEntry.COLUMN_WINE_IMAGE)));

bitmap = null;
    try {
        //If the cursor does not have anything in the image column, set the image to null, with a height so the textviews look decent
        if (cursor.isNull(cursor.getColumnIndexOrThrow(WineContract.WineEntry.COLUMN_WINE_IMAGE))){
            mFullImage.setImageBitmap(null);
            mFullImage.setMaxHeight(300);
        }else{
            //remake the bitmap from the URI in the image column
      //********This next line is where the program crashes**********
            bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), imageURI);
            mFullImage.setImageBitmap(bitmap);

        }

Could I get some help with figuring out how to apply this to my project?

andrdoiddev
  • 349
  • 1
  • 3
  • 15
  • Start with removing the addFlags() from the used intent. Makes no sense. – greenapps Oct 24 '17 at 18:05
  • After that you should take a persistable uri permission in onActivityResult. Where is your code? – greenapps Oct 24 '17 at 18:05
  • `imageURI = Uri.parse(cursor.getString(cursor.getColumnIndexOrThrow(WineContract.WineEntry.COLUMN_WINE_IMAGE)));`. My god... Should we know what uri you are taking there? You are supposed to use code that is understandable for everybody. – greenapps Oct 24 '17 at 18:09
  • @greenapps Sorry, I just didn't think the result of that line specifically would have helped anyone. Maybe I was mistaken? Here is the Log TAG that gets retreived from that line: "DetailsActivity: imageURI: content://com.android.providers.media.documents/document/image%3A46421" – andrdoiddev Oct 24 '17 at 18:11
  • Yes that is better. You should have used it in your code straight away. – greenapps Oct 24 '17 at 18:13
  • @greenapps Sorry about that. I just to make the post as detailed as I can (with relevant information), but I'm pretty new to all this yet so I'm not 100% positive on what is releavant or not. – andrdoiddev Oct 24 '17 at 18:15

4 Answers4

14

To handle permission results overide onRequestPermissionsResult as below

 @Override
 public void onRequestPermissionsResult (int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case General.REQUESTPERMISSION:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //reload my activity with permission granted or use the features that required the permission

            } else {
                Messenger.makeToast(getContext(), R.string.noPermissionMarshmallow);
            }
            break;
    }
}

and to persist the permission implement as shown below in your onActivityResult method

@Override
public void onActivityResult (int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    //if ok user selected a file
    if (resultCode == RESULT_OK) {
        Uri sourceTreeUri = data.getData();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            getContext().getContentResolver().takePersistableUriPermission(sourceTreeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        }
    }
}
Richard Muvirimi
  • 639
  • 9
  • 14
  • 2
    There are no runtime permissions needed with `ACTION_OPEN_DOCUMENT`. – ianhanniballake Oct 24 '17 at 18:11
  • I just added the "if (Build.VERSION..." statement from the onActivityResult section and that fixed the issue. So basically this code (if I'm understanding it correctly) is just telling it that if the version of Android is higher than Lollipop, it needs to get the persistant permission for that specific URI so it can open up even after restarts. And thats because versions newer than Lollipop switched it up to use "content:" instead, which is a bit more locked down. (Sorry, I'm just trying to give a bit more explanation for myself) – andrdoiddev Oct 24 '17 at 18:27
  • @Richard Muvirimi, could I get a bit of an explanation on why it has to be Lollipop or newer? Is there a reason this wouldn't work on Kitkat, for example? – andrdoiddev Oct 24 '17 at 18:34
  • Yes that's completely correct. You should change the lollipop part to Kitkat since that's when the storage access framework was introduced – Richard Muvirimi Oct 24 '17 at 18:34
10

In onActivityResult(), call takePersistableUriPermission() on a ContentResolver, passing in the Uri that you got along with the mode flag(s) that indicate what access you want (read, write, both).

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 2
    Got `java.lang.SecurityException: No persistable permission grants found for UID 10522 and Uri content://com.android.externalstorage.documents/document/primary:Pictures/___.jpg [user 0]` – Pat Lee Jun 08 '21 at 12:07
  • @PatLee: You might want to ask a separate Stack Overflow question where you can provide a [mcve] showing how you are making the `ACTION_OPEN_DOCUMENT` request, plus where and how you are calling `takePersistableUriPermission()`. – CommonsWare Jun 08 '21 at 12:12
7

a bit late but... We need to provide persistent Uri permission. Instead of doing it on onActivityResult as oppose to prior answers, I prefer to add it as a flag:

val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).also {
    it.addCategory(Intent.CATEGORY_OPENABLE)
    it.type = "image/*"
    it.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
    it.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}

Also, a note worth mentioning is persistent permission is available only to Intent.ACTION_OPEN_DOCUMENT and NOT Intent.ACTION_GET_CONTENT whereas the latter one is like a one-time thing.

Dr.jacky
  • 3,341
  • 6
  • 53
  • 91
guness
  • 6,336
  • 7
  • 59
  • 88
  • 3
    To persist access to the 'uri' you still need: `getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)` in `onActivityResult` – Mike Hanafey Feb 23 '20 at 17:12
5

The permission denial issue needs to be dealt with the first time you receive a URI. If you use registerForActivityResult() instead of startActivityForResult()

    // Kotlin
    private val pickImage = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result: ActivityResult ->
        if (result.resultCode == Activity.RESULT_OK) {
            //  you will get result here in result.data
            val uri = result.data?.data!!
            requireActivity().contentResolver.takePersistableUriPermission(
                uri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION
            )
            // Do something else with the URI. E.g, save the URI as a string in the database
        }
    }
Booo
  • 71
  • 1
  • 6