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>
enAndroidManifest.xml
en la etiqueta<application>
. Especifique una autoridad única para el atributoandroid: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 carpetares/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:
-
targetSdkVersion
tutargetSdkVersion
a 23 o menos, o -
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.
- Para Android N, generamos nuevo contenido: // url basada en uri que apunta al archivo.
- 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..."));