Denegación de permiso al compartir archivos con FileProvider [duplicate]

7 minutos de lectura

Avatar de usuario de Paweł Bęza
Paweł Beza

Estoy tratando de compartir archivos con FileProvider. Verifiqué que el archivo se comparte correctamente con aplicaciones como Gmail, Google Drive, etc. Aunque se lanza la siguiente excepción:

2019-08-28 11:43:03.169 12573-12595/com.example.name E/DatabaseUtils: Writing exception to parcel
    java.lang.SecurityException: Permission Denial: reading androidx.core.content.FileProvider uri content://com.example.name.provider/external_files/Android/data/com.example.name/files/allergy_report.pdf from pid=6005, uid=1000 requires the provider be exported, or grantUriPermission()
        at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:729)
        at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:602)
        at android.content.ContentProvider$Transport.query(ContentProvider.java:231)
        at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:104)
        at android.os.Binder.execTransactInternal(Binder.java:1021)
        at android.os.Binder.execTransact(Binder.java:994)

proveedor:

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

file_provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="." />
</paths>

Compartir intención

Intent intentShareFile = new Intent(Intent.ACTION_SEND);
File fileWithinMyDir = new File(targetPdf);

if (fileWithinMyDir.exists()) {
    intentShareFile.setType("application/pdf");
    Uri uri = FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID + ".provider", fileWithinMyDir);
    intentShareFile.putExtra(Intent.EXTRA_STREAM, uri);
    intentShareFile.putExtra(Intent.EXTRA_SUBJECT, "Sharing File...");
    intentShareFile.putExtra(Intent.EXTRA_TEXT, "Sharing File...");
    intentShareFile.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    startActivity(Intent.createChooser(intentShareFile, "Share File"));
}

Espero que pueda señalar mi error de por qué se lanzan estas excepciones cuando parece que a las aplicaciones se les otorga el permiso correctamente y el uso compartido funciona como debería ser.

EDITAR:

Descubrí que el problema radica en la línea:

startActivity(Intent.createChooser(intentShareFile, "Share File"));

Cuando lo cambié simplemente a

startActivity(intentShareFile);

Sin embargo, muestra un diseño un poco diferente para la aplicación de recolección. Pero aún no puedo entender por qué el selector original no funciona.

  • stackoverflow.com/a/74052302/987762 para la respuesta

    – kalandar

    13 oct 2022 a las 8:04

Lo siento por la respuesta tardía. Lo resolví así:

Intent chooser = Intent.createChooser(intentShareFile, "Share File");

List<ResolveInfo> resInfoList = this.getPackageManager().queryIntentActivities(chooser, PackageManager.MATCH_DEFAULT_ONLY);

for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName;
    this.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}

startActivity(chooser);

Esto me ayudó: Denegación de permiso con el proveedor de archivos a través de la intención

  • ¡Impresionante! Punto clave que inicialmente me perdí: pase el selector, no la intención de consultar IntentActivities ()

    – lukas hansen

    23 de febrero de 2020 a las 21:34


  • ¡Dios mío, he estado lidiando con esto durante días! Esto resolvió mi problema y no tengo idea de por qué simplemente lo copié y pegué jaja. ¡Voy a leer lo que me salvó el trasero! ¡Gracias!

    – NoobCoderChick

    24 de febrero de 2020 a las 4:43

  • Lo extraño es que tengo 4 vistas de imágenes en la misma actividad y cuando haces clic en la primera (cualquiera de ellas) aparece la aplicación de la cámara, tomas la foto y se guarda en la vista de imagen sin problemas… la segunda haga clic, aparece la cámara, toma la foto y cuando selecciona la marca de verificación, se bloquea la aplicación. Lo que también es súper extraño es que el archivo de ruta que está en los documentos de Android no funciona y tengo que usar “/” como ruta… no sé.

    – NoobCoderChick

    24 de febrero de 2020 a las 4:45

  • Y que hay con intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); donde esta uris ArrayList?

    – t0m

    16 de diciembre de 2020 a las 11:08

  • Esto funciona para el objetivo 31

    – iadcialim24

    17 de abril de 2022 a las 9:38

Avatar de usuario de Nit
Liendre

También hay una manera de otorgar el permiso URI a través de la bandera Intent, sin hacerlo manualmente con grantUriPermission() y manteniendo el uso de Intent.createChooser().

Él Intención.createChooser() la documentación dice:

Si la intención de destino ha especificado FLAG_GRANT_READ_URI_PERMISSION o FLAG_GRANT_WRITE_URI_PERMISSION, estas banderas también se establecerán en la intención del selector devuelto, con su ClipData establecido de manera adecuada: ya sea un reflejo directo de getClipData() si no es nulo, o un nuevo ClipData creado a partir de obtener datos().

Por lo tanto, si el Intent original tiene el Uri establecido en su ClipData o en setData()debería funcionar como quería.

En tu ejemplo, el ACCIÓN_ENVIAR Intent admite el conjunto de Uri a través de setClipData() a partir de Jelly Bean (Android 4.1).

Para resumir, aquí hay un ejemplo funcional de su código:

Intent intentShareFile = new Intent(Intent.ACTION_SEND);
File fileWithinMyDir = new File(targetPdf);

if(fileWithinMyDir.exists()) {
    String mimeType = "application/pdf";
    String[] mimeTypeArray = new String[] { mimeType };
    
    intentShareFile.setType(mimeType);
    Uri uri = FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID + ".provider", fileWithinMyDir);
    
    // Add the uri as a ClipData
    intentShareFile.setClipData(new ClipData(
        "A label describing your file to the user",
        mimeTypeArray,
        new ClipData.Item(uri)
    ));
    
    // EXTRA_STREAM is kept for compatibility with old applications
    intentShareFile.putExtra(Intent.EXTRA_STREAM, uri);
    
    intentShareFile.putExtra(Intent.EXTRA_SUBJECT,
            "Sharing File...");
    intentShareFile.putExtra(Intent.EXTRA_TEXT, "Sharing File...");
    intentShareFile.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    startActivity(Intent.createChooser(intentShareFile, "Share File"));
}

  • ¡Gracias! Solución perfecta sin necesidad de consultar actividades.

    – Derek K.

    19 de febrero de 2021 a las 9:00

  • Cuál es el path utilizada en ClipData.Item ? Lo intenté uri.pathpero la excepción todavía existe.

    – Samuel T. Chou

    16 de febrero de 2022 a las 8:35

  • Uy, mi mal @SamuelT.Chou, deberías poner directamente el Uri al crear el ClipData.Item(). He editado mi respuesta con la corrección.

    – liendres

    17 de febrero de 2022 a las 9:31


Avatar de usuario de Harmen
Harmen

Además de la respuesta de Nit anterior, es posible configurar el ClipData directamente con el para ser compartido uri como sigue.

intent.setClipData(ClipData.newRawUri("", uri));

Esto evita la excepción de seguridad al presentar el selector de intenciones.

Si desea compartir varios uri, puede configurar los datos de clip con varios uri para evitar la excepción de seguridad. Para resumir:


Intent intent = new Intent();
intent.setType(mimeType);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

if (uris.size() == 0) { 
   return;
}
else if (uris.size() == 1) {
   Uri uri = uris.get(0);
   intent.setAction(Intent.ACTION_SEND);
   intent.putExtra(Intent.EXTRA_STREAM, uri);
   intent.setClipData(ClipData.newRawUri("", uri));   
}
else {
  intent.setAction(Intent.ACTION_SEND_MULTIPLE);
  intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
  ClipData clipData = ClipData.newRawUri("", uris.get(0));

  for (int i = 1; i < uris.size(); i++) { 
      Uri uri = uris.get(i); 
      clipData.addItem(new ClipData.Item(uri));
  }
  intent.setClipData(clipData);

}

startActivity(Intent.createChooser(intent, title));

Avatar de usuario de MOHO LEARNIN G
MOHO APRENDIZAJE

Esto funcionó para mí.

    Intent sharableIntent = new Intent();
    sharableIntent.setAction(Intent.ACTION_SEND);
    sharableIntent.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION );

    Uri imageUri = Uri.parse(ImgLoc);
    File imageFile = new File(String.valueOf(imageUri));
    Uri UriImage = FileProvider.getUriForFile(context, Author, imageFile);

    sharableIntent.setType("image/*");
    sharableIntent.putExtra(Intent.EXTRA_STREAM, UriImage);
    sharableIntent.putExtra(Intent.EXTRA_TITLE, title);
    sharableIntent.putExtra(Intent.EXTRA_TEXT, body);

    Intent chooser = Intent.createChooser(sharableIntent, "Chooser Title");
    chooser.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
    context.startActivity(chooser);

crearChooser
Intent.createChooser(): Construye un nuevo Intent ACTION_CHOOSER que envuelve el intent de destino dado, y opcionalmente proporciona un título. Si la intención de destino ha especificado FLAG_GRANT_READ_URI_PERMISSION o FLAG_GRANT_WRITE_URI_PERMISSION, estas banderas también se establecerán en la intención del selector devuelto, con su ClipData establecido de manera adecuada: ya sea un reflejo directo de getClipData() si no es nulo, o un nuevo ClipData creado a partir de obtener datos()

¿Ha sido útil esta solución?