¿Cómo se usa FileProvider para compartir contenido con otras aplicaciones?

Estoy buscando una forma de compartir (no ABRIR) correctamente un archivo interno con una aplicación externa utilizando el FileProvider de la biblioteca de soporte de Android .

Siguiendo el ejemplo en los documentos,

   

y el uso de ShareCompat para compartir un archivo a otras aplicaciones de la siguiente manera:

 ShareCompat.IntentBuilder.from(activity) .setStream(uri) // uri from FileProvider .setType("text/html") .getIntent() .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 

no funciona, ya que FLAG_GRANT_READ_URI_PERMISSION solo concede permiso para el URI especificado en los data de la intención, no el valor de EXTRA_STREAM extra (como fue establecido por setStream ).

Traté de poner en peligro la seguridad al configurar android:exported FileProvider a true para el proveedor, pero FileProvider verifica internamente si se exporta, cuando es así, arroja una excepción.

Al utilizar FileProvider desde la biblioteca de soporte, debe otorgar y revocar permisos manualmente (en tiempo de ejecución) para que otras aplicaciones lean Uri específico. Utilice los métodos Context.grantUriPermission y Context.revokeUriPermission .

Por ejemplo:

 //grant permision for app with package "packegeName", eg. before starting other app via intent context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); //revoke permisions context.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); 

Como último recurso, si no puede proporcionar el nombre del paquete, puede otorgar el permiso a todas las aplicaciones que puedan manejar una intención específica:

 //grant permisions for all apps that can handle given intent Intent intent = new Intent(); intent.setAction(Intent.ACTION_SEND); ... List 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); } 

Método alternativo según la documentación :

  • Coloque el URI de contenido en un Intento llamando a setData ().
  • Luego, llame al método Intent.setFlags () con FLAG_GRANT_READ_URI_PERMISSION o FLAG_GRANT_WRITE_URI_PERMISSION o con ambos.
  • Finalmente, envíe el Intento a otra aplicación. La mayoría de las veces, haces esto llamando a setResult ().

    Los permisos otorgados en un bash permanecen vigentes mientras la stack de la actividad receptora está activa. Cuando la stack termina, el
    los permisos se eliminan automáticamente Permisos otorgados a uno
    La actividad en una aplicación de cliente se extiende automáticamente a otra
    componentes de esa aplicación.

Por cierto. si es necesario, puede copiar el origen de FileProvider y cambiar el método attachInfo para evitar que el proveedor attachInfo si se exporta.

Esta solución funciona para mí desde OS 4.4. Para que funcione en todos los dispositivos, agregué una solución alternativa para los dispositivos más antiguos. Esto asegura que siempre se use la solución más segura.

Manifest.xml:

     

file_paths.xml:

    

Java:

 public static void sendFile(Context context) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); String dirpath = context.getFilesDir() + File.separator + "directory"; File file = new File(dirpath + File.separator + "file.txt"); Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file); intent.putExtra(Intent.EXTRA_STREAM, uri); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // Workaround for Android bug. // grantUriPermission also needed for KITKAT, // see https://code.google.com/p/android/issues/detail?id=76683 if (Build.VERSION.SDK_INT < = Build.VERSION_CODES.KITKAT) { List resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; context.grantUriPermission(packageName, attachmentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } } if (intent.resolveActivity(context.getPackageManager()) != null) { context.startActivity(intent); } } public static void revokeFileReadPermission(Context context) { if (Build.VERSION.SDK_INT < = Build.VERSION_CODES.KITKAT) { String dirpath = context.getFilesDir() + File.separator + "directory"; File file = new File(dirpath + File.separator + "file.txt"); Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file); context.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } } 

El permiso se revocó con revokeFileReadPermission () en los métodos onResume y onDestroy () del Fragment o la Activity.

Código de trabajo completo muestra cómo compartir el archivo desde la carpeta de la aplicación interna. Probado en Android 7 y Android 5.

AndroidManifest.xml

  ....     

xml / provider_paths

 < ?xml version="1.0" encoding="utf-8"?>    

Código en sí

  File imagePath = new File(getFilesDir(), "external_files"); imagePath.mkdir(); File imageFile = new File(imagePath.getPath(), "test.jpg"); // Write data in your file Uri uri = FileProvider.getUriForFile(this, getPackageName(), imageFile); Intent intent = ShareCompat.IntentBuilder.from(this) .setStream(uri) // uri from FileProvider .setType("text/html") .getIntent() .setAction(Intent.ACTION_VIEW) //Change if needed .setDataAndType(uri, "image/*") .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(intent); 

Dado que, como dice Phil en su comentario sobre la pregunta original, esto es único y no hay otra información sobre SO on en google, pensé que también debería compartir mis resultados:

En mi aplicación, FileProvider resolvió compartir archivos usando la intención de compartir. No fue necesaria una configuración o código especial, más allá de eso para configurar FileProvider. En mi manifest.xml coloqué:

     

En my_paths.xml tengo:

    

En mi código tengo:

  Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.setType("application/xml"); Uri uri = FileProvider.getUriForFile(this, "com.my.apps.package.files", fileToShare); shareIntent.putExtra(Intent.EXTRA_STREAM, uri); startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.share_file))); 

Y puedo compartir mi tienda de archivos en mi almacenamiento privado de aplicaciones con aplicaciones como Gmail y Google Drive sin ningún problema.

Por lo que puedo decir, esto solo funcionará en las versiones más nuevas de Android, por lo que probablemente tengas que encontrar una forma diferente de hacerlo. Esta solución funciona para mí en 4.4, pero no en 4.0 o 2.3.3, por lo que esta no será una forma útil de compartir contenido para una aplicación que se debe ejecutar en cualquier dispositivo Android.

En manifest.xml:

    

Tome nota cuidadosamente de cómo especifica las autoridades. Debe especificar la actividad desde la cual creará el URI e iniciará el bash de compartir, en este caso la actividad se llama SharingActivity. ¡Este requisito no es obvio en los documentos de Google!

file_paths.xml:

 < ?xml version="1.0" encoding="utf-8"?>    

Tenga cuidado de cómo especifica la ruta. Lo anterior está predeterminado en la raíz de su almacenamiento interno privado.

En SharingActivity.java:

 Uri contentUri = FileProvider.getUriForFile(getActivity(), "com.mydomain.myapp.SharingActivity", myFile); Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.setType("image/jpeg"); shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(Intent.createChooser(shareIntent, "Share with")); 

En este ejemplo, estamos compartiendo una imagen JPEG.

Finalmente, probablemente sea una buena idea asegurarse de que ha guardado el archivo correctamente y de que puede acceder a él con algo como esto:

 File myFile = getActivity().getFileStreamPath("mySavedImage.jpeg"); if(myFile != null){ Log.d(TAG, "File found, file description: "+myFile.toString()); }else{ Log.w(TAG, "File not found!"); } 

En mi aplicación, FileProvider funciona perfectamente y puedo adjuntar archivos internos almacenados en el directorio de archivos a clientes de correo electrónico como Gmail, Yahoo, etc.

En mi manifiesto como se menciona en la documentación de Android coloqué:

    

Y como mis archivos estaban almacenados en el directorio de archivos raíz, los filepaths.xml fueron los siguientes:

    

Ahora en el código:

  File file=new File(context.getFilesDir(),"test.txt"); Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE); shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Test"); shareIntent.setType("text/plain"); shareIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[] {"email-address you want to send the file to"}); Uri uri = FileProvider.getUriForFile(context,"com.package.name.fileprovider", file); ArrayList uris = new ArrayList(); uris.add(uri); shareIntent .putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); try { context.startActivity(Intent.createChooser(shareIntent , "Email:").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } catch(ActivityNotFoundException e) { Toast.makeText(context, "Sorry No email Application was found", Toast.LENGTH_SHORT).show(); } } 

Esto funcionó para mí. Espero que esto ayude 🙂

solo para mejorar la respuesta dada anteriormente: si obtiene NullPointerEx:

también puedes usar getApplicationContext () sin contexto

  List resInfoList = getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; grantUriPermission(packageName, photoURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); } 

Si obtienes una imagen de la cámara, ninguna de estas soluciones funciona para Android 4.4. En este caso, es mejor verificar las versiones.

 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (intent.resolveActivity(getContext().getPackageManager()) != null) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { uri = Uri.fromFile(file); } else { uri = FileProvider.getUriForFile(getContext(), getContext().getPackageName() + ".provider", file); } intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); startActivityForResult(intent, CAMERA_REQUEST); } 

Quiero compartir algo que nos bloqueó durante un par de días: el código del proveedor de archivos DEBE insertarse entre las tags de la aplicación, no después de ella. Puede ser trivial, pero nunca se especifica, ¡y pensé que podría haber ayudado a alguien! (gracias de nuevo a piolo94)