Android - поставщик файлов - отказ в разрешении

У меня есть два приложения: app1 и app2.

App2 имеет:

<provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.android.provider.ImageSharing"
        android:exported="false"
        android:grantUriPermissions="true" >
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/paths" />
</provider>

paths.xml:

<paths>

     <files-path name="my_images" path="images/"/>

</paths>

App2 получает запрос в своей деятельности из App1, чтобы получить URI для изображения. Активность App2 делает следующее, как только URI определяется:

Intent intent = new Intent();

intent.setDataAndType(contentUri, getContentResolver().getType(contentUri));

int uid = Binder.getCallingUid();
String callingPackage = getPackageManager().getNameForUid(uid);

getApplicationContext().grantUriPermission(callingPackage, contentUri,
                    Intent.FLAG_GRANT_READ_URI_PERMISSION);

setResult(Activity.RESULT_OK, intent);
finish();

При получении результата из App2, App1 выполняет следующие действия:

Uri imageUri = data.getData();
if(imageUri != null) {
    ImageView iv = (ImageView) layoutView.findViewById(R.id.imageReceived);
    iv.setImageURI(imageUri);
}

В App1, возвращаясь из App2, я получаю следующее исключение:

java.lang.SecurityException: отказ в разрешении: открытие провайдер android.support.v4.content.FileProvider от ProcessRecord {52a99eb0 3493: com.android.App1.app/u0a57} (pid = 3493, uid = 10057), который не экспортируется из uid 10058

Что я делаю неправильно?

Ответ 1

Оказывается, единственный способ решить это - предоставить разрешения всем пакетам, которые могут ему понадобиться, например:

List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName;
    context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}

Ответ 2

Во-первых, я попытался бы отключиться от grantUriPermission() и просто поставить FLAG_GRANT_READ_URI_PERMISSION на Intent через addFlags() или setFlag().

Если по какой-то причине это не сработает, вы можете попробовать переместить логику getCallingUid() в onCreate() вместо того, где она есть, и посмотреть, можете ли вы найти там фактического "вызывающего".

Ответ 3

Android <= Lollipop (API 22)

Есть отличная статья Лоренцо Квироли, которая решает эту проблему для более старых версий Android.

Он обнаружил, что вам нужно вручную установить ClipData для Intent и установить разрешения для него, например, так:

if ( Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP ) {
    takePictureIntent.setClipData( ClipData.newRawUri( "", photoURI ) );
    takePictureIntent.addFlags( Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_READ_URI_PERMISSION );
}

Я проверил это на API 17, и он отлично работал. Не удалось найти решение, где бы ни работало.

Ответ 4

Просто добавьте setData(contentUri); а также на основе требования добавить addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); или addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

Это разрешает java.lang.SecurityException: отказ от разрешения

Проверенный.

Это делается согласно https://developer.android.com/reference/android/support/v4/content/FileProvider.html#Permissions

Ответ 5

Как вы можете захватить изображение с помощью камеры на Nougat, используя File Provider.

Чтобы прочитать о провайдере файла по этой ссылке Provider файла

и кит кат и зефир следуйте этим шагам. Прежде всего, провайдер рекламных тегов под тегом приложения в MainfestFile.

 <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>

создайте имя файла с помощью (provider_paths.xml) в папке res enter image description here

<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>

Я решил эту проблему, это идет на версии Kit KitKat

private void takePicture() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        Uri photoURI = null;
        try {
            File photoFile = createImageFileWith();
            path = photoFile.getAbsolutePath();
            photoURI = FileProvider.getUriForFile(MainActivity.this,
                    getString(R.string.file_provider_authority),
                    photoFile);

        } catch (IOException ex) {
            Log.e("TakePicture", ex.getMessage());
        }
        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
            takePictureIntent.setClipData(ClipData.newRawUri("", photoURI));
            takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
        startActivityForResult(takePictureIntent, PHOTO_REQUEST_CODE);
    }
}

  private File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ENGLISH).format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_DCIM), "Camera");
    File image = File.createTempFile(
            imageFileName,  /* prefix */
            ".jpg",         /* suffix */
            storageDir      /* directory */
    );

    // Save a file: path for use with ACTION_VIEW intents
    mCurrentPhotoPath = "file:" + image.getAbsolutePath();
    return image;
}

Ответ 6

Спасибо, @CommonsWare за этот совет.

Моя проблема была с пакетом вызова. По какой-то причине Binder.callingUid() и getPackageManager().getNameForUid(uid) давали мне имя пакета App2 вместо App1.

Я пытался вызвать его в App2 onCreate а также onResume, но без радости.

Я использовал следующее, чтобы решить это:

getApplicationContext().grantUriPermission(getCallingPackage(), 
          contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);

Оказывается, у деятельности есть специальный API для этого. Смотрите здесь

Ответ 7

Я решил проблему таким образом:

        Intent sIntent = new Intent("com.appname.ACTION_RETURN_FILE").setData(uri);
        List<ResolveInfo> resInfoList = activity.getPackageManager().queryIntentActivities(sIntent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            activity.grantUriPermission(FILE_PROVIDER_ID, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
        sIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        activity.setResult(RESULT_OK, sIntent);

Ответ 8

Вам нужно установить разрешение определенного имени пакета, после чего вы сможете получить к нему доступ.

context.grantUriPermission("com.android.App1.app", fileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

Ответ 9

java.lang.SecurityException: отказ от разрешения:...

Смотрите fooobar.com/info/202492/... (Android добавила новую модель разрешения для Android 6.0 Marshmallow)

Запросить разрешения во время выполнения:

String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
requestPermissions(permissions, WRITE_REQUEST_CODE);