Android KitKat securityException when trying to read from MediaStore

Solution 1:

Had the same problem for the last couple of days. Tried a few solutions, but for some reason I was getting permission denial on Uri content provider for some images even when I had the android.permission.MANAGE_DOCUMENTS permission added to my manifest.

Here's a workaround, for the time being:

i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(i, CHOOSE_IMAGE);

This forces the older image gallery to open instead of the new Kitkat documents view.

Now, you can get the Uri by calling the following in your onActivityResult:

Uri selectedImageURI = data.getData();

Hope this helps.

Solution 2:

In the Intent used to prompt the user to add a file, use Intent.ACTION_OPEN_DOCUMENT (on KitKat API 19 or higher) instead of Intent.ACTION_GET_CONTENT or Intent.ACTION_PICK. Then, when you want to use the Uri, take the previous persistent permissions that were set by Intent.ACTION_OPEN_DOCUMENT as described in @feri's link ( https://developer.android.com/guide/topics/providers/document-provider.html#permissions):

final int takeFlags = data.getFlags()
        & (Intent.FLAG_GRANT_READ_URI_PERMISSION
        | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Check for the freshest data.
getContentResolver().takePersistableUriPermission(originalUri, takeFlags);

You can read more about the differences between Intent.ACTION_GET_CONTENT, Intent.ACTION_PICK, and Intent.ACTION_OPEN_DOCUMENT here: http://developer.android.com/reference/android/content/Intent.html#ACTION_OPEN_DOCUMENT

Solution 3:

OK, I found a working sample that gets past the issue:

            intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
            intent.addCategory(Intent.CATEGORY_OPENABLE);
            intent.setType("image/*");
            startActivityForResult(intent, myClassConstant.SELECT_PICTURE);

Here's what I've learned...this is a change in KitKat, and this code only works on API 19 and up. It will not work for older versions so you need to have methods for pre-KitKat as well. Once you get the file using the Category_Openable argument, in my case an image, you can then use it by referencing it as the URI. In my case, I store the URIString (file path) and I'm able to do what I want with it after the open occurs.

The issue I'm now trying to figure out is how long the file permissions persist. It appears that it expired since yesterday, as my code looks for the UriString, then tries to open the file. If I use the code above to select the image, it works - it stores the name, and I was able to exit the program, go back in and it still worked...however, my first run of the program this morning (after not touching it in 12+ hours), it acted like it couldn't find the file (and I'm sure if watching through the debugger, I'd see the non-critical error appear again.

So now the question is: How do we persist the permissions once I know what the file (name) is?

Solution 4:

I assume the Uri you want to show references an image from a provider which implements the new Storage Access Framework introduced with Android 4.4 KitKat. This can be Gallery as well as Google-Drive. The only way to access theese documents seems to be to use the system file picker. If the user selects files for opening, the system grants permission to the app, which started the file open dialog. Theese permissions are valid as long as the device is not rebootet. Other images can not be accessed and lead to the Security Exception.

If the Uri is be persisted, then the rights granted to access the Uri must also be persisted. For more details look here: https://developer.android.com/guide/topics/providers/document-provider.html#permissions

Solution 5:

The MANAGE_DOCUMENTS permission can be hold only by the system :

The MANAGE_DOCUMENTS permission. By default a provider is available to everyone. Adding this permission restricts your provider to the system. This restriction is important for security.

(taken from Storage Access Framework)

So by using this permission in your DocumentProvider you are restricting the access to your provider to only the system. What this means is that only the System UI picker will be able to access your files. So in order for an app to pick a file from your Provider it will have to launch an Intent with ACTION_OPEN_DOCUMENT, which will in fact launch the System UI picker, the user will choose a file from there, and then the URI will be returned to you.