through studio nougat item intent geturi getdata fileuriexposedexception fileprovider exposed error clipdata beyond app android android-file android-7.0-nougat

android - studio - exposed beyond app through clipdata.item.geturi() camera



android.os.FileUriExposedException: archivo:///storage/emulated/0/test.txt expuesto más allá de la aplicación a través de Intent.getData() (23)

La aplicación se bloquea cuando intento abrir un archivo. Funciona debajo de Android Nougat, pero en Android Nougat se bloquea. Solo se bloquea cuando intento abrir un archivo desde la tarjeta SD, no desde la partición del sistema. ¿Algún problema de permiso?

Código de muestra:

File file = new File("/storage/emulated/0/test.txt"); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(file), "text/*"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); // Crashes on this line

Iniciar sesión:

android.os.FileUriExposedException: archivo: ///storage/emulated/0/test.txt expuesto más allá de la aplicación a través de Intent.getData ()

Editar:

Cuando se dirige a Android Nougat, file:// ya no se permiten URI. Deberíamos usar content:// URI en su lugar. Sin embargo, mi aplicación necesita abrir archivos en directorios raíz. ¿Algunas ideas?


Además de la solución que usa FileProvider , hay otra forma de solucionar esto. Simplemente pon

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());

en Application.onCreate() . De esta manera, la máquina virtual ignora la exposición del archivo URI .

Método

builder.detectFileUriExposure()

habilita la comprobación de exposición de archivos, que también es el comportamiento predeterminado si no configuramos una VmPolicy.

Encontré un problema de que si uso un content:// URI para enviar algo, algunas aplicaciones simplemente no pueden entenderlo. Y no se permite degradar la versión del target SDK . En este caso mi solución es útil.

Actualizar:

Como se mencionó en el comentario, StrictMode es una herramienta de diagnóstico y no se debe utilizar para este problema. Cuando publiqué esta respuesta hace un año, muchas aplicaciones solo pueden recibir archivos uris. Simplemente se bloquean cuando intento enviarles un uri de FileProvider. Esto se soluciona en la mayoría de las aplicaciones ahora, por lo que deberíamos ir con la solución FileProvider.


Aquí mi solución:

en Manifiesto.xml

<application android:name=".main.MainApp" android:allowBackup="true" android:icon="@drawable/ic_app" android:label="@string/application_name" android:logo="@drawable/ic_app_logo" android:theme="@style/MainAppBaseTheme"> <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/provider_paths"/> </provider>

en res / xml / 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>

en mi fragmento tengo el siguiente código:

Uri myPhotoFileUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", myPhotoFile); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.putExtra(MediaStore.EXTRA_OUTPUT, myPhotoFileUri);

Eso es todo lo que necesitas.

Tampoco es necesario crear

public class GenericFileProvider extends FileProvider {}

Pruebo en Android 5.0, 6.0 y Android 9.0 y es un trabajo exitoso.


En mi caso, SetDataAndType la excepción al reemplazar SetDataAndType solo con SetData .


La respuesta de @Pkosta es una forma de hacerlo.

Además de usar FileProvider , también puede insertar el archivo en MediaStore (especialmente para archivos de imagen y video), porque los archivos en MediaStore son accesibles para todas las aplicaciones:

MediaStore está dirigido principalmente a tipos MIME de video, audio e imagen, sin embargo, a partir de Android 3.0 (nivel de API 11) también puede almacenar tipos no multimedia (consulte MediaStore.Files para obtener más información). Los archivos se pueden insertar en MediaStore usando scanFile () después de lo cual se pasa un Uri content: // style adecuado para compartir a la devolución de llamada proporcionada onScanCompleted (). Tenga en cuenta que una vez agregado al sistema MediaStore, el contenido es accesible para cualquier aplicación en el dispositivo.

Por ejemplo, puede insertar un archivo de video en MediaStore como este:

ContentValues values = new ContentValues(); values.put(MediaStore.Video.Media.DATA, videoFilePath); Uri contentUri = context.getContentResolver().insert( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);

contentUri es como content://media/external/video/media/183473 , que se puede pasar directamente a Intent.putExtra :

intent.setType("video/*"); intent.putExtra(Intent.EXTRA_STREAM, contentUri); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); activity.startActivity(intent);

Esto funciona para mí y ahorra las molestias de usar FileProvider .


La respuesta de @palash k es correcta y funcionó para archivos de almacenamiento interno, pero en mi caso también quiero abrir archivos de almacenamiento externo, mi aplicación se bloqueó cuando abrí un archivo de almacenamiento externo como sdcard y usb, pero logré resolver el problema modificando provider_paths.xml de la respuesta aceptada

cambiar el provider_paths.xml como a continuación

<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path path="Android/data/${applicationId}/" name="files_root" /> <root-path name="root" path="/" /> </paths>

y en la clase java (Sin cambios ya que la respuesta aceptada es solo una pequeña edición)

Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)

Esto me ayuda a solucionar el bloqueo de archivos de almacenamientos externos, espero que esto ayude a alguien que tenga el mismo problema que el mío :)


Mi solución fue ''Uri.parse'' la ruta del archivo como cadena, en lugar de usar Uri.fromFile ().

String storage = Environment.getExternalStorageDirectory().toString() + "/test.txt"; File file = new File(storage); Uri uri; if (Build.VERSION.SDK_INT < 24) { uri = Uri.fromFile(file); } else { uri = Uri.parse(file.getPath()); // My work-around for new SDKs, causes ActivityNotFoundException in API 10. } Intent viewFile = new Intent(Intent.ACTION_VIEW); viewFile.setDataAndType(uri, "text/plain"); startActivity(viewFile);

Parece que fromFile () usa un puntero de archivo A, lo que supongo que podría ser inseguro cuando las direcciones de memoria están expuestas a todas las aplicaciones. Pero una cadena de ruta de archivo nunca daña a nadie, por lo que funciona sin lanzar FileUriExposedException.

Probado en los niveles API 9 a 27! Abre con éxito el archivo de texto para editarlo en otra aplicación. No requiere FileProvider, ni la biblioteca de soporte de Android en absoluto.


No sé por qué, hice todo exactamente igual que Pkosta ( share ) pero seguí recibiendo errores:

java.lang.SecurityException: Permission Denial: opening provider redacted from ProcessRecord{redacted} (redacted) that is not exported from uid redacted

Perdí horas en este tema. ¿El culpable? Kotlin

val playIntent = Intent(Intent.ACTION_VIEW, uri) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

intent era establecer getIntent().addFlags lugar de operar en mi playIntent recientemente declarado.


Para descargar pdf del servidor, agregue el código a continuación en su clase de servicio. Espero que esto te ayude.

File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName + ".pdf"); intent = new Intent(Intent.ACTION_VIEW); //Log.e("pathOpen", file.getPath()); Uri contentUri; contentUri = Uri.fromFile(file); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); if (Build.VERSION.SDK_INT >= 24) { Uri apkURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", file); intent.setDataAndType(apkURI, "application/pdf"); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } else { intent.setDataAndType(contentUri, "application/pdf"); }

Y sí, no olvide agregar permisos y proveedor en su manifiesto.

<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application <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> </application>


Primero debe agregar un proveedor a su AndroidManifest

<application ...> <activity> .... </activity> <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.your.package.fileProvider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application>

ahora cree un archivo en la carpeta de recursos xml (si usa android studio puede presionar Alt + Enter después de resaltar file_paths y seleccionar crear una opción de recurso xml)

Luego en el archivo file_paths ingrese

<?xml version="1.0" encoding="utf-8"?> <paths> <external-path path="Android/data/com.your.package/" name="files_root" /> <external-path path="." name="external_storage_root" /> </paths>

Este ejemplo es para una ruta externa que puede consultar developer.android.com/reference/android/support/v4/content/… para obtener más opciones. Esto le permitirá compartir archivos que están en esa carpeta y su subcarpeta.

Ahora todo lo que queda es crear la intención de la siguiente manera:

MimeTypeMap mime = MimeTypeMap.getSingleton(); String ext = newFile.getName().substring(newFile.getName().lastIndexOf(".") + 1); String type = mime.getMimeTypeFromExtension(ext); try { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Uri contentUri = FileProvider.getUriForFile(getContext(), "com.your.package.fileProvider", newFile); intent.setDataAndType(contentUri, type); } else { intent.setDataAndType(Uri.fromFile(newFile), type); } startActivityForResult(intent, ACTIVITY_VIEW_ATTACHMENT); } catch (ActivityNotFoundException anfe) { Toast.makeText(getContext(), "No activity found to open this attachment.", Toast.LENGTH_LONG).show(); }

EDITAR : agregué la carpeta raíz de la tarjeta SD en file_paths. He probado este código y funciona.


Puse este método para que la ruta imageuri ingrese fácilmente en el contenido.

enter code here public Uri getImageUri(Context context, Bitmap inImage) { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); inImage.compress(Bitmap.CompressFormat.PNG, 100, bytes); String path = MediaStore.Images.Media.insertImage(context.getContentResolver(), inImage, "Title", null); return Uri.parse(path); }


Sé que esta es una pregunta bastante antigua, pero esta respuesta es para futuros espectadores. Entonces encontré un problema similar y después de investigar, encontré una alternativa a este enfoque.

Su intención aquí, por ejemplo: para ver su imagen desde su camino en Kotlin

val intent = Intent() intent.setAction(Intent.ACTION_VIEW) val file = File(currentUri) intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) val contentURI = getContentUri(context!!, file.absolutePath) intent.setDataAndType(contentURI,"image/*") startActivity(intent)

Función principal a continuación

private fun getContentUri(context:Context, absPath:String):Uri? { val cursor = context.getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, arrayOf<String>(MediaStore.Images.Media._ID), MediaStore.Images.Media.DATA + "=? ", arrayOf<String>(absPath), null) if (cursor != null && cursor.moveToFirst()) { val id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID)) return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, Integer.toString(id)) } else if (!absPath.isEmpty()) { val values = ContentValues() values.put(MediaStore.Images.Media.DATA, absPath) return context.getContentResolver().insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) } else { return null } }

Del mismo modo, en lugar de una imagen, puede usar cualquier otro formato de archivo como pdf y, en mi caso, funcionó bien


Si targetSdkVersion es superior a 24 , FileProvider se usa para otorgar acceso.

Cree un archivo xml (Ruta: res / xml) 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>


Agregar un proveedor en AndroidManifest.xml

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

Si está utilizando androidx , la ruta de FileProvider debe ser:

android:name="androidx.core.content.FileProvider"

y reemplazar

Uri uri = Uri.fromFile(fileImagePath);

a

Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);

Editar: mientras incluye el URI con una Intent asegúrese de agregar la siguiente línea:

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

y eres bueno para ir Espero eso ayude.


Si su targetSdkVersion >= 24 , entonces tenemos que usar la clase FileProvider para dar acceso al archivo o carpeta en particular para que sean accesibles para otras aplicaciones. Creamos nuestra propia clase heredando FileProvider para asegurarnos de que FileProvider no entre en conflicto con FileProviders declarados en dependencias importadas como se describe here .

Pasos para reemplazar el file:// URI con content:// URI:

  • Agregar una clase que extienda FileProvider

    public class GenericFileProvider extends FileProvider {}

  • Agregue una etiqueta FileProvider <provider> en AndroidManifest.xml en la etiqueta <application> . Especifique una autoridad única para el atributo android:authorities para evitar conflictos, las dependencias importadas pueden especificar ${applicationId}.provider y otras autoridades de uso común.

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" ... <application ... <provider android:name=".GenericFileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider> </application> </manifest>

  • Luego cree un archivo provider_paths.xml en la carpeta res/xml . Es posible que se necesite crear una carpeta si no existe. El contenido del archivo se muestra a continuación. Describe que nos gustaría compartir el acceso al almacenamiento externo en la carpeta raíz (path=".") Con el nombre external_files .

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

  • El último paso es cambiar la línea de código a continuación en

    Uri photoURI = Uri.fromFile(createImageFile());

    a

    Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile());

  • Editar: si está utilizando una intención para hacer que el sistema abra su archivo, es posible que deba agregar la siguiente línea de código:

    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

Consulte, el código completo y la solución se han explicado here.


Si su targetSdkVersion es 24 o superior, no puede usar file: valores de file: Uri en Intents en dispositivos Android 7.0+ .

Sus elecciones son:

  1. targetSdkVersion tu targetSdkVersion a 23 o menos, o

  2. Ponga su contenido en almacenamiento interno, luego developer.android.com/reference/android/support/v4/content/… para que esté disponible selectivamente para otras aplicaciones

Por ejemplo:

Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f)); i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(i);

(de este proyecto de muestra )


Si su aplicación está dirigida a API 24+, y aún quiere / necesita usar file: // intentos, puede usar una forma hacky para deshabilitar la verificación de tiempo de ejecución:

if(Build.VERSION.SDK_INT>=24){ try{ Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure"); m.invoke(null); }catch(Exception e){ e.printStackTrace(); } }

El método StrictMode.disableDeathOnFileUriExposure está oculto y documentado como:

/** * Used by lame internal apps that haven''t done the hard work to get * themselves off file:// Uris yet. */

El problema es que mi aplicación no es aburrida, sino que no quiere quedar paralizada usando content: // intenciones que muchas aplicaciones no entienden. Por ejemplo, abrir un archivo mp3 con content: // esquema ofrece muchas menos aplicaciones que al abrirlo sobre file: // esquema. No quiero pagar las fallas de diseño de Google limitando la funcionalidad de mi aplicación.

Google quiere que los desarrolladores usen el esquema de contenido, pero el sistema no está preparado para esto, durante años las aplicaciones se hicieron para usar Archivos que no son "contenido", los archivos se pueden editar y guardar de nuevo, mientras que los archivos que se sirven sobre el esquema de contenido no se pueden (pueden ¿ellos?).


Simplemente pegue el siguiente código en la actividad onCreate() .

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());

Ignorará la exposición a URI.

Feliz codificación :-)


Simplemente pegue el siguiente código en la actividad onCreate ()

StrictMode.VmPolicy.Builder builder = nuevo StrictMode.VmPolicy.Builder (); StrictMode.setVmPolicy (builder.build ());

Ignorará la exposición a URI


Simplemente pegue el siguiente código en la actividad onCreate ()

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());

Ignorará la exposición a URI


Usar el fileProvider es el camino a seguir. Pero puede usar esta solución simple:

ADVERTENCIA : Se solucionará en la próxima versión de Android: https://issuetracker.google.com/issues/37122890#comment4

reemplazar:

startActivity(intent);

por

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


Utilicé la respuesta de Palash dada anteriormente, pero estaba algo incompleta, tuve que proporcionar un permiso como este

Intent intent = new Intent(Intent.ACTION_VIEW); Uri uri; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { uri = FileProvider.getUriForFile(this, getPackageName() + ".provider", new File(path)); List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); } }else { uri = Uri.fromFile(new File(path)); } intent.setDataAndType(uri, "application/vnd.android.package-archive"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent);


Xamarin.Android

Nota: La ruta xml / provider_paths.xml (.axml) no se pudo resolver, incluso después de crear la carpeta xml en Recursos (tal vez se puede colocar en una ubicación existente como Valores , no lo intenté), así que recurrí a esto que funciona por ahora. Las pruebas mostraron que solo es necesario invocarlo una vez por ejecución de la aplicación (lo cual tiene sentido ya que cambia el estado operativo de la VM del host).

Nota: xml debe estar en mayúscula, por lo que Resources / Xml / provider_paths.xml

Java.Lang.ClassLoader cl = _this.Context.ClassLoader; Java.Lang.Class strictMode = cl.LoadClass("android.os.StrictMode"); System.IntPtr ptrStrictMode = JNIEnv.FindClass("android/os/StrictMode"); var method = JNIEnv.GetStaticMethodID(ptrStrictMode, "disableDeathOnFileUriExposure", "()V"); JNIEnv.CallStaticVoidMethod(strictMode.Handle, method);


https://.com/a/38858040/395097 esta respuesta está completa.

Esta respuesta es para: ya tiene una aplicación que estaba dirigida a menos de 24, y ahora está actualizando a targetSDKVersion> = 24.

En Android N, solo se cambia el archivo uri expuesto a la aplicación de terceros. (No de la forma en que lo estábamos usando antes). Así que cambie solo los lugares donde comparte la ruta con la aplicación de terceros (Cámara en mi caso)

En nuestra aplicación, estábamos enviando uri a la aplicación Cámara, en esa ubicación esperamos que la aplicación de la cámara almacene la imagen capturada.

  1. Para Android N, generamos nuevo contenido: // url basada en uri que apunta al archivo.
  2. Generamos la ruta basada en la API del archivo habitual para el mismo (utilizando un método anterior).

Ahora tenemos 2 uri diferentes para el mismo archivo. # 1 se comparte con la aplicación de la cámara. Si la intención de la cámara es exitosa, podemos acceder a la imagen desde el n. ° 2.

Espero que esto ayude.


agregue estas dos líneas en onCreate

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());

Método de compartir

File dir = new File(Environment.getExternalStorageDirectory(), "ColorStory"); File imgFile = new File(dir, "0.png"); Intent sendIntent = new Intent(Intent.ACTION_VIEW); sendIntent.setType("image/*"); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + imgFile)); sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(Intent.createChooser(sendIntent, "Share images..."));