Error de FileProvider en los dispositivos Huawei

Tengo una excepción que sólo ocurre en los dispositivos Huawei en mi aplicación cuando se utiliza FileProvider.getUriForFile :

 Exception: java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/<card name>/Android/data/<app package>/files/.export/2016-10-06 13-22-33.pdf at android.support.v4.content.FileProvider$SimplePathStrategy.getUriForFile(SourceFile:711) at android.support.v4.content.FileProvider.getUriForFile(SourceFile:400) 

Aquí está la definición de mi proveedor de archivos en mi manifiesto:

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

El archivo de recursos con rutas configuradas:

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

¿Alguna idea sobre la causa de este problema y por qué sucede sólo en los dispositivos Huawei? ¿Cómo depuraría esto, dado que no tengo un dispositivo Huawei?

ACTUALIZAR:

He añadido más registros a mi aplicación y tengo algunos resultados inconsistentes al imprimir ContextCompat.getExternalFilesDirs y context.getExternalFilesDir en estos dispositivos:

 ContextCompat.getExternalFilesDirs: /storage/emulated/0/Android/data/<package>/files /storage/sdcard1/Android/data/<package>/files context.getExternalFilesDir: /storage/sdcard1/Android/data/<package>/files 

Esto es inconsistente con la documentación de ContextCompat.getExternalFilesDirs que indica que The first path returned is the same as getExternalFilesDir(String)

Eso explica el problema ya que uso context.getExternalFilesDir en mi código y FileProvider utiliza ContextCompat.getExternalFilesDirs .

Actualización para Android N (dejando la respuesta original a continuación y han confirmado que este nuevo enfoque funciona en producción):

Como observó en su actualización, muchos modelos de dispositivos Huawei (por ejemplo, KIW-L24, ALE-L21, ALE-L02, PLK-L01 y una variedad de otros) rompen el contrato de Android para llamadas a ContextCompat#getExternalFilesDirs(String) . En lugar de devolver el Context#getExternalFilesDir(String) (es decir, la entrada predeterminada) como el primer objeto de la matriz, en su lugar devuelven el primer objeto como la ruta a la tarjeta SD externa, si está presente.

Al romper este contrato de pedido, estos dispositivos Huawei con tarjetas SD externas se bloquearán con una IllegalArgumentException en las llamadas a FileProvider#getUriForFile(Context, String, File) para raíces de external-files-path . Si bien hay una variedad de soluciones que puede perseguir para tratar de abordar este problema (por ejemplo, escribir una aplicación personalizada FileProvider ), he encontrado el método más fácil es coger este problema y:

  • Pre-N: Devuelve Uri#fromFile(File) , que no funcionará con Android N y superior debido a FileUriExposedException
  • N: Copie el archivo a su cache-path (nota: esto puede introducir ANRs si se hace en el UI Thread) y luego devolver FileProvider#getUriForFile(Context, String, File) para el archivo copiado (es decir, evitar el bug por completo)

Código para lograr esto se puede encontrar a continuación:

 public class ContentUriProvider { private static final String HUAWEI_MANUFACTURER = "Huawei"; public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, @NonNull File file) { if (HUAWEI_MANUFACTURER.equalsIgnoreCase(Build.MANUFACTURER)) { Log.w(ContentUriProvider.class.getSimpleName(), "Using a Huawei device Increased likelihood of failure..."); try { return FileProvider.getUriForFile(context, authority, file); } catch (IllegalArgumentException e) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { Log.w(ContentUriProvider.class.getSimpleName(), "Returning Uri.fromFile to avoid Huawei 'external-files-path' bug for pre-N devices", e); return Uri.fromFile(file); } else { Log.w(ContentUriProvider.class.getSimpleName(), "ANR Risk -- Copying the file the location cache to avoid Huawei 'external-files-path' bug for N+ devices", e); // Note: Periodically clear this cache final File cacheFolder = new File(context.getCacheDir(), HUAWEI_MANUFACTURER); final File cacheLocation = new File(cacheFolder, file.getName()); InputStream in = null; OutputStream out = null; try { in = new FileInputStream(file); out = new FileOutputStream(cacheLocation); // appending output stream IOUtils.copy(in, out); Log.i(ContentUriProvider.class.getSimpleName(), "Completed Android N+ Huawei file copy. Attempting to return the cached file"); return FileProvider.getUriForFile(context, authority, cacheLocation); } catch (IOException e1) { Log.e(ContentUriProvider.class.getSimpleName(), "Failed to copy the Huawei file. Re-throwing exception", e1); throw new IllegalArgumentException("Huawei devices are unsupported for Android N", e1); } finally { IOUtils.closeQuietly(in); IOUtils.closeQuietly(out); } } } } else { return FileProvider.getUriForFile(context, authority, file); } } } 

Junto con el file_provider_paths.xml :

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

Una vez que haya creado una clase como esta, reemplace sus llamadas a:

 FileProvider.getUriForFile(Context, String, File) 

con:

 ContentUriProvider.getUriForFile(Context, String, File) 

Francamente, no creo que esta sea una solución especialmente graciosa, pero sí nos permite usar un comportamiento formalmente documentado de Android sin hacer nada demasiado drástico (por ejemplo, escribir una implementación personalizada de FileProvider ). He probado esto en la producción, así que puedo confirmar que resuelve estos desplomes de Huawei. Para mí, este fue el mejor enfoque, ya que no quería pasar demasiado tiempo abordar lo que es, obviamente, un defecto del fabricante.

Actualizar desde antes de los dispositivos de Huawei con este error actualizado a Android N:

Esto no funcionará con Android N y superior debido a FileUriExposedException , pero todavía tengo que encontrar un dispositivo Huawei con esta configuración errónea en Android N.

 public class ContentUriProvider { private static final String HUAWEI_MANUFACTURER = "Huawei"; public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, @NonNull File file) { if (HUAWEI_MANUFACTURER.equalsIgnoreCase(Build.MANUFACTURER) && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { Log.w(ContentUriProvider.class.getSimpleName(), "Using a Huawei device on pre-N. Increased likelihood of failure..."); try { return FileProvider.getUriForFile(context, authority, file); } catch (IllegalArgumentException e) { Log.w(ContentUriProvider.class.getSimpleName(), "Returning Uri.fromFile to avoid Huawei 'external-files-path' bug", e); return Uri.fromFile(file); } } else { return FileProvider.getUriForFile(context, authority, file); } } } 

Tuve el mismo problema y mi solución al final era siempre utilizar la llamada ContextCompat.getExternalFilesDirs para construir el File que se utiliza como parámetro para FileProvider . De esta manera, no tiene que utilizar ninguna de las soluciones anteriores.

En otras palabras. Si tiene control sobre el parámetro File que utiliza para llamar a FileProvider y / o no le importa que el archivo pueda terminar siendo guardado fuera de la carpeta clásica /storage/emulated/0/Android/data/ (que debería ser Perfectamente bien, ya que es todo lo mismo la tarjeta SD), entonces sugiero hacer lo que he hecho.

Si no es su caso, entonces sugiero utilizar la respuesta anterior con la implementación personalizada getUriForFile .

Mi solución a este problema en este momento, incluso si no es perfecto, es declarar mi FileProvider con la siguiente ruta (para poder servir todos los archivos en el dispositivo):

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

Esto no está documentado oficialmente y podría romper con una futura versión de la biblioteca de soporte v4, pero no puedo ver ninguna otra solución para servir un archivo en el almacenamiento externo secundario (a menudo la tarjeta SD) utilizando el FileProvider existente.

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.