【问题标题】:Attach PDF - Java.Lang.SecurityException: Permission Denial附加 PDF - Java.Lang.SecurityException:权限被拒绝
【发布时间】:2023-03-03 01:11:02
【问题描述】:

注意:我已经回答了关于类似例外的其他问题。这不是重复的 - 它们都不是关于选择 PDF 或这个特定错误的。

系好安全带。

我们正在开发一个 Xamarin.Android 项目。有一个'附加文档' 活动。

当用户尝试(从下载目录)选择 PDF 时,会引发以下异常:

Java.Lang.SecurityException:权限拒绝:读取 com.android.providers.downloads.DownloadStorageProvider uri content://com.android.providers.downloads.documents/226 来自 pid=13877, uid=10282 要求您使用 ACTION_OPEN_DOCUMENT 或相关 API

文件选择器意图:

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 方法:

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);
    }
}

检查文件目录 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 方法:

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>

我们最近添加了运行时权限(在启动屏幕活动中)以处理 Android 23(即 6.0 - marshmallow)及更高版本。

权限字段和数组:

const int REQUEST = 0;

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

CheckPermissions 方法:

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 方法:

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 方法:

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;
}

我希望有一个 TL;DR: 版本,但我不知道问题出在哪里,因为我们的代码与该操作的其他示例代码几乎相似。我读了一些关于持久化 URI 和授予权限的内容;我尝试了其中的一些-它们没有用。并且没有完全理解一些 - this one 是其中之一。

谢谢。

【问题讨论】:

标签: android xamarin xamarin.android android-permissions


【解决方案1】:

例外情况很明显。您需要在声明此活动的AndroidManifest.xml 文件中设置android:exported="true"。目前设置为false..

下面试试

<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>

【讨论】:

  • 抛出以下异常:Java.Lang.RuntimeException: Unable to get provider android.support.v4.content.FileProvider: java.lang.SecurityException: Provider must not be exported
  • 要授予对文件的选择访问权限,您可以通过 FLAG_GRANT_READ_URI_PERMISSION 和/或 FLAG_GRANT_WRITE_URI_PERMISSION 在用于将提供商的 Uri 值之一传递给第三方应用程序的 Intent 中执行此操作(例如,通过ACTION_VIEW Intent 与startActivity() 一起使用)。
  • 更多详情请参阅此处stackoverflow.com/questions/24090667/…
  • 我已经在文件选择器意图中添加了这些标志。请参考问题中的代码。
  • 您可能没有正确使用它。查看此链接medium.com/@quiro91/…了解更多详情...
猜你喜欢
  • 2011-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-28
相关资源
最近更新 更多