0

Note: I have gone through other questions about similar exceptions. This is not a duplicate - none of them are about picking PDF or this particular error.

Buckle in.

We are working on a Xamarin.Android project. There is an 'attach document' activity.

When the user tries to select a PDF (from the downloads directory), it throws the following exception:

Java.Lang.SecurityException: Permission Denial: reading com.android.providers.downloads.DownloadStorageProvider uri content://com.android.providers.downloads.documents/226 from pid=13877, uid=10282 requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs

File-picker intent:

ChooseFile.Click += (sender, e) =>
{
    Intent intent = new Intent();
    intent.SetType("*/*");
    intent.SetAction(Intent.ActionOpenDocument);
    intent.AddCategory(Intent.CategoryOpenable);

    if (global::Android.OS.Build.VERSION.SdkInt >= global::Android.OS.BuildVersionCodes.N)
    {
        intent.AddFlags(ActivityFlags.GrantReadUriPermission);
        intent.AddFlags(ActivityFlags.GrantWriteUriPermission);
        intent.AddFlags(ActivityFlags.GrantPersistableUriPermission);
        intent.PutExtra(Intent.ExtraLocalOnly, true);
        intent.AddFlags(ActivityFlags.NoHistory);
    }

    try
    {
        StartActivityForResult(Intent.CreateChooser(intent, "Select File"), FILE_SELECT_CODE);
    }
    catch (Exception ex)
    {
        Toast.MakeText(this, "Please install a File Manager.", ToastLength.Short).Show();
    }

};

OnActivityResult method:

protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
    base.OnActivityResult(requestCode, resultCode, data);

    if ((requestCode == FILE_SELECT_CODE) && (resultCode == Result.Ok) && (data != null))
    {
        // Get the Uri of the selected file 
        uri = data.Data;
        string path = "";

        bool isdoc = DocumentsContract.IsDocumentUri(this, uri);
        if (isdoc)
        {
            if (IsDownloadsDocument(uri))
            {
                string id = DocumentsContract.GetDocumentId(uri);

                global::Android.Net.Uri contentUri = null;

                if (global::Android.OS.Build.VERSION.SdkInt >= global::Android.OS.BuildVersionCodes.N)
                {
                    contentUri = ContentUris.WithAppendedId(global::Android.Net.Uri.Parse("content://com.android.providers.downloads.documents"), Convert.ToInt64(id));
                }
                else
                {
                    contentUri = ContentUris.WithAppendedId(global::Android.Net.Uri.Parse("content://downloads/public_downloads"), Convert.ToInt64(id));
                }

                path = GetDataColumn(this, contentUri, null, null);

            }
            else if (IsMediaDocument(uri))
            {
                string docId = DocumentsContract.GetDocumentId(uri);
                string[] split = docId.Split(':');

                string type = split[0];

                global::Android.Net.Uri contentUri = null;
                if ("image".Equals(type))
                {
                    contentUri = MediaStore.Images.Media.ExternalContentUri;
                }
                else if ("video".Equals(type))
                {
                    contentUri = MediaStore.Video.Media.ExternalContentUri;
                }
                else if ("audio".Equals(type))
                {
                    contentUri = MediaStore.Audio.Media.ExternalContentUri;
                }

                string selection = "_id=?";
                string[] selectionArgs = new String[]
                {
                    split[1]
                };

                path = GetDataColumn(this, contentUri, selection, selectionArgs);
            }
        }
        else
        {
            path = GetRealPathFromURI(global::Android.Net.Uri.Parse(getImageUrlWithAuthority(this, uri)));
        }

        txtFileName.Text = System.IO.Path.GetFileName(path);
        fileStream = File.ReadAllBytes(path);
    }
}

Checking file directory URI:

private bool IsExternalStorageDocument(global::Android.Net.Uri uri)
{
    return "com.android.externalstorage.documents".Equals(uri.Authority);
}

private bool IsDownloadsDocument(global::Android.Net.Uri uri)
{
    return "com.android.providers.downloads.documents".Equals(uri.Authority);
}

private bool IsMediaDocument(global::Android.Net.Uri uri)
{
    return "com.android.providers.media.documents".Equals(uri.Authority);
}

private bool IsGooglePhotosUri(global::Android.Net.Uri uri)
{
    return "com.google.android.apps.photos.content".Equals(uri.Authority);
}

GetDataColumn method:

private string GetDataColumn(Context context, global::Android.Net.Uri uri, String selection, string[] selectionArgs)
{
    ICursor cursor = null;
    string column = "_data";
    string[] projection =
    {
        column
    };

    try
    {
        cursor = context.ContentResolver.Query(uri, projection, selection, selectionArgs, null);
        if (cursor != null && cursor.MoveToFirst())
        {
            int index = cursor.GetColumnIndexOrThrow(column);
            return cursor.GetString(index);
        }
    }
    finally
    {
        if (cursor != null)
            cursor.Close();
    }
    return null;
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.jnbmedical.portal"
          android:installLocation="auto"
          android:versionCode="33"
          android:versionName="3.3">

    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28" />

    <application android:label="JandB" android:icon="@drawable/icon">
        <provider android:name="android.support.v4.content.FileProvider"
              android:authorities="com.jnbmedical.portal.fileprovider"
              android:exported="false"
              android:grantUriPermissions="true">
            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
                 android:resource="@xml/file_provider_paths" />
        </provider>
    </application>

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
    <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
    <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <uses-permission android:name="com.jandbmedical.portal.testapp.permission.C2D_MESSAGE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />

</manifest>

file_provider_paths.xml

<?xml version="1.0" encoding="utf-8" ?>
<paths>
  <external-path
      name="external_files" path="." />
</paths>

We recently added run-time permissions (in the splash-screen activity) to handle Android 23 (i.e. 6.0 - marshmallow) and higher.

Permission fields & array:

const int REQUEST = 0;

readonly string[] PERMISSIONS =
{
    Manifest.Permission.AccessCoarseLocation,
    Manifest.Permission.AccessFineLocation,
    Manifest.Permission.ReadExternalStorage,
    Manifest.Permission.WriteExternalStorage,
    Manifest.Permission.ReadPhoneState,
};

CheckPermissions method:

private void CheckPermissions()
{
    if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadPhoneState) != Permission.Granted
        || ContextCompat.CheckSelfPermission(this, Manifest.Permission.AccessFineLocation) != Permission.Granted
        || ContextCompat.CheckSelfPermission(this, Manifest.Permission.AccessCoarseLocation) != Permission.Granted
        || ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadExternalStorage) != Permission.Granted
        || ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != Permission.Granted)
    {
        //Permissions have not been granted
        ActivityCompat.RequestPermissions(this, PERMISSIONS, REQUEST);
    }
    else
    {
        //Permissions have been granted
        TelephonyManager tm = (TelephonyManager)GetSystemService(TelephonyService);
        Util.UUID = tm.DeviceId;

        StartActivity(typeof(MainActivity));
        Finish();
    }
}

OnRequestPermissionsResult method:

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
    switch (requestCode)
    {
        case REQUEST:
            {
                var permissionGranted = VerifyPermissions(grantResults);
                if (permissionGranted)
                {
                    TelephonyManager tm = (TelephonyManager)GetSystemService(TelephonyService);
                    Util.UUID = tm.DeviceId;

                    StartActivity(typeof(MainActivity));
                    Finish();
                }
                else
                {
                    this.ShowInformationWithClick(Resources.GetText(Resource.String.Alert), Resources.GetText(Resource.String.OK), "App requires storage, location, and phone access to function properly. Please grant necessary permissions. App will not work if permissions are not granted.", HandleClicked);
                }
            }
            break;
    }
}

VerifyPermissions method:

public static bool VerifyPermissions(Permission[] grantResults)
{
    // At least one result must be checked.
    if (grantResults.Length < 1)
        return false;

    // Verify that each required permission has been granted, otherwise return false.
    foreach (Permission result in grantResults)
    {
        if (result != Permission.Granted)
        {
            return false;
        }
    }
    return true;
}

I wish there was a TL;DR: version of this but I do not know what the problem is because our code is almost similar to other sample codes out there for this operation. I read something about persistable URIs and granting permissions; I tried some of those - they didn't work. And didn't fully understand some - this one being one of them.

Thanks.

Ronak Vachhani
  • 214
  • 2
  • 14
  • 1
    I don't see anywhere where you call `GrantUriPermission` for the file you want to share. – Cheesebaron Oct 02 '18 at 06:36
  • Possible duplicate of [How to use support FileProvider for sharing content to other apps?](https://stackoverflow.com/questions/18249007/how-to-use-support-fileprovider-for-sharing-content-to-other-apps) – Cheesebaron Oct 02 '18 at 06:37
  • @Cheesebaron - According to the documentation, you don't really need the `GrantUriPermission` method. Check out the remarks at: [GrantUriPermission Remarks](https://developer.xamarin.com/api/member/Android.Content.Context.GrantUriPermission/#Remarks) – Ronak Vachhani Oct 02 '18 at 07:05
  • @Cheesebaron - But just to make sure and to try it out anyway, where exactly would I call the method? – Ronak Vachhani Oct 02 '18 at 07:14

1 Answers1

-1

The exception is clear. You need to set android:exported="true" in your AndroidManifest.xml file where you declare this Activity. which is set to false at present..

try below

<application android:label="JandB" android:icon="@drawable/icon">
    <provider android:name="android.support.v4.content.FileProvider"
          android:authorities="com.jnbmedical.portal.fileprovider"
          android:exported="true"  <!-- change here -->
          android:grantUriPermissions="true">
        <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
             android:resource="@xml/file_provider_paths" />
    </provider>
</application>
amit
  • 709
  • 6
  • 17
  • 1
    Throws the following exception: `Java.Lang.RuntimeException: Unable to get provider android.support.v4.content.FileProvider: java.lang.SecurityException: Provider must not be exported` – Ronak Vachhani Oct 02 '18 at 07:01
  • to give select access to files, you can do this by `FLAG_GRANT_READ_URI_PERMISSION` and/or `FLAG_GRANT_WRITE_URI_PERMISSION`, in the Intent that you use to pass one of your provider's Uri values to the third-party app (e.g., via an `ACTION_VIEW` Intent used with `startActivity()`). – amit Oct 02 '18 at 09:47
  • refer here https://stackoverflow.com/questions/24090667/how-can-make-a-fileprovider-available-to-other-applications for more details – amit Oct 02 '18 at 09:48
  • I am already adding those flags with the file-picker intent. Please refer the code in the question. – Ronak Vachhani Oct 02 '18 at 11:32
  • you might not be using it correctly.. check this link https://medium.com/@quiro91/sharing-files-through-intents-part-2-fixing-the-permissions-before-lollipop-ceb9bb0eec3a for more details... – amit Oct 03 '18 at 10:47