Search code examples
androidandroid-contentproviderandroid-contentresolverstorage-access-framework

takePersistableUriPermission via ACTION_OPEN_DOCUMENT fails on a custom documents provider but only for API < 26


I have a custom DocumentsProvider implementation that works flawlessly for a user to choose photos or videos for use by the app, as long as the Android API is 26 or greater. Using APIs 21-25 I get a security error similar to what is described in this SO post. However I am already doing everything mentioned in that post as a solution.

Manifest entry:

    <provider
        android:name=".storageproviders.FacebookProvider"
        android:authorities="${facebookDocumentsAuthority}"
        android:exported="true"
        android:grantUriPermissions="true"
        android:permission="android.permission.MANAGE_DOCUMENTS">
        <intent-filter>
            <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
        </intent-filter>
    </provider>

My Intent setup looks like this:

    Context context = InTouch.getInstance().getApplicationContext();
    Intent contentSelectionIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
    contentSelectionIntent.setType("*/*");

The launcher that references this intent looks like this:

    mLaunchFileChooserIntent = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            new ActivityResultCallback<ActivityResult>() {
                @Override
                public void onActivityResult(ActivityResult result) {
                    if ( result.getResultCode() == Activity.RESULT_OK) {
                        Intent initialIntent = result.getData();
                        if ( initialIntent != null) {
                            // This may have one or more files
                            ArrayList<Uri> uriList = new ArrayList<>();
                            Uri uri = initialIntent.getData();
                            if (uri != null) {
                                // Single file chosen
                                uriList.add(uri);
                            } else {
                            *
                            *
                            *
                            }
                            // I then call my utility method handleMediaClipData() passing the following as the argument for 'takeFlags':
                            // (initialIntent.getFlags() & URI_PERMISSIONS_FLAGS)
                            

Use of that launcher is done as the response to a button press, like this:

    mFiles.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            closeFABMenu();                   
            mLaunchFileChooserIntent.launch(contentSelectionIntent);
        }
    });

I have validated that using an emulator running API 21 that the intent that comes back from the SAF chooser (initialIntent above) has its flags set to 0x43, which is correct - 0x40 is the Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION which was given by my provider via the manifest entry at the top of this post, and 3 is the combo of values (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION).

Yet when I try to use the takePersistableUriPermission() API using the associated Uri returned by that intent along with the flags argument from above for the 'takeFlags' argument (initialIntent.getFlags() & URI_PERMISSIONS_FLAGS) like this:

public static Disposable handleMediaClipData(final MediaCreate mediaCreate,
                                             final ArrayList<Uri> mediaUris,
                                             final int takeFlags,
                                             final String listId,
                                             final AppCompatActivity activity) {
    Context context = InTouch.getInstance().getApplicationContext();
    final ContentResolver resolver = context.getContentResolver();
    *
    *
    *
    resolver.takePersistableUriPermission(uri, takeFlags);
    *
    *
    *

I get the following security error:

java.lang.SecurityException: No persistable permission grants found for UID 10084

The exact same code flow (app ui, custom DocumentsProvider) on both an emulator and physical device using API 26 or greater WORKS.

If I try my app using API 21 using SAF and choosing something from the phone outside of the app specific area (and thus not incorporating my custom DocumentsProvider), it ALSO WORKS.

Therefore, I assume it is something I am not doing in the custom DocumentsProvider for devices running API 21-25 but I have no idea what that might be.

What is different for implementing a custom DocumentsProvider that might affect the ability to take persistable permissions for APIs 21 to 25 vs. 26 and higher?

I assume from the error there is some sort of reconciliation going on under the covers to match a Uri that is returned from the chooser with the Intent that originally launched the chooser, but I don't understand how that relationship works, where to start looking to debug it, or if I'm even on the right track with that assumption.


Solution

  • There isn't anything wrong with your implementation of DocumentsProvider, it's the expected behavior on API 19-25 when working with SAF.

    Even if you get a SecurityException while trying to take persistable URI permission you'd still always have access to URIs exposed from your own DocumentsProvider.

    Thus it'd be a good idea to catch and ignore the SecurityException specially from your own URIs.

    Note: If your app contains a DocumentsProvider and also persists URIs returned from ACTION_OPEN_DOCUMENT, ACTION_OPEN_DOCUMENT_TREE, or ACTION_CREATE_DOCUMENT, be aware that you won’t be able to persist access to your own URIs via takePersistableUriPermission() — despite it failing with a SecurityException, you’ll always have access to URIs from your own app. You can add the boolean EXTRA_EXCLUDE_SELF to your Intents if you want to hide your own DocumentsProvider(s) on API 23+ devices for any of these actions.

    Here's a note from official Android Developers blog that confirms this behavior - https://medium.com/androiddevelopers/building-a-documentsprovider-f7f2fb38e86a