I have an exception that happens only on Huawei devices in my app when using 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)
Here is the definition of my file provider in my manifest:
<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>
The resource file with configured paths:
<?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>
Any idea on the cause of this issue and why it happens only on Huawei devices? How would I debug this, given that I don't have a Huawei device?
UPDATE:
I've added more logs into my app and I got some inconsistent results when printing both ContextCompat.getExternalFilesDirs
and context.getExternalFilesDir
on these devices:
ContextCompat.getExternalFilesDirs: /storage/emulated/0/Android/data/<package>/files /storage/sdcard1/Android/data/<package>/files context.getExternalFilesDir: /storage/sdcard1/Android/data/<package>/files
This is inconsistent with the documentation of ContextCompat.getExternalFilesDirs
that states that The first path returned is the same as getExternalFilesDir(String)
That explains the issue since I use context.getExternalFilesDir
in my code and FileProvider
uses ContextCompat.getExternalFilesDirs
.
fileprovider . To get a content URI for the file default_image. jpg in the images/ subdirectory of your internal storage add the following code: File imagePath = new File(Context.
To make FileProvider work follow these three steps: Define the FileProvider in your AndroidManifest file. Create an XML file that contains all paths that the FileProvider will share with other applications. Bundle a valid URI in the Intent and activate it.
Update for Android N (leaving the original answer below and have confirmed this new approach works in production):
As you noted in your update, many Huawei device models (e.g. KIW-L24, ALE-L21, ALE-L02, PLK-L01, and a variety of others) break the Android contract for calls to ContextCompat#getExternalFilesDirs(String)
.
Rather than returning Context#getExternalFilesDir(String)
(ie the default entry) as the first object in the
array, they instead return the first object as the path to the external SD card, if one is present.
By breaking this ordering contract, these Huawei devices with external SD cards will crash with an IllegalArgumentException
on calls to FileProvider#getUriForFile(Context, String, File)
for external-files-path
roots. While there are a variety of solutions that you can pursue to attempt to deal with this issue (e.g. writing a custom FileProvider
implementation), I've found the easiest approach is to catch this issue and:
Uri#fromFile(File)
, which won't work with Android N and above due to FileUriExposedException
cache-path
(note: this can introduce ANRs if done on the UI Thread) and then return FileProvider#getUriForFile(Context, String, File)
for the copied file (ie avoiding the bug altogether)Code to accomplish this can be found below:
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); } } }
Along with the 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>
Once you've created a class like this, replace your calls to:
FileProvider.getUriForFile(Context, String, File)
with:
ContentUriProvider.getUriForFile(Context, String, File)
Frankly, I don't think this is an especially graceful solution, but it does allow us to use formally documented Android behavior without doing anything too drastic (e.g. writing a custom FileProvider
implementation). I have tested this in production, so I can confirm that it resolves these Huawei crashes. For me, this was the best approach, since I didn't wish to spend too much time addressing what is quite obviously a manufacturer's defect.
Update from before Huawei devices with this bug updated to Android N:
This won't work with Android N and above due to FileUriExposedException
, but I have yet to encounter a Huawei device with this mis-configuration on 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); } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With