Search code examples
javaandroidkotlinandroidxandroid-fileprovider

FileProvider - java.lang.IllegalArgumentException: Failed to find configured root that contains


I am trying to expose a URI using FileProvider, but keep running into this exception:

java.lang.IllegalArgumentException: Failed to find configured root that contains /data/data/com.example.android.development/cache/images/background.png

AndroidManifest.xml:

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

provider_paths.xml:

<paths>
    <cache-path name="images" path="images/"/>
</paths>

Saving the file:

fun cache(context: Context, bitmap: Bitmap, fileName: String) {
    val filePath = File(context.cacheDir, "images")
    val file = File(filePath, fileName)
    try {
        filePath.mkdirs()
        val fos = FileOutputStream(file)
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
        fos.close()
    } catch (e: IOException) {
        throw RuntimeException(e)
    }
}

Getting the URI:

fun getUri(context: Context, fileName: String): Uri {
    val filePath = File(context.cacheDir, "images")
    val file = File(filePath, fileName)
    return FileProvider.getUriForFile(context, "${context.applicationContext.packageName}.provider", file)
}

I did some debugging and it seems that the FileProvider getUriForFile(File file) function first converts the file path /data/user/0/com.example.android.development/cache/images/background.png to the canonical file path /data/data/com.example.android.development/cache/images/background.png, then tries to match the root path /storage/emulated/0 with the canonical file path. When it does not find this match, it throws the exception. I'm not sure what I'm doing wrong to cause this or how to fix this?

Debugging FileProvider.java getUriForFile(File file):

@Override
public Uri getUriForFile(File file) { // file: "/data/user/0/com.example.android.development/cache/images/background.png"
    String path; // path: "/data/data/com.example.android.development/cache/images/background.png"
    try {
        path = file.getCanonicalPath();
    } catch (IOException e) {
        throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
    }

    // Find the most-specific root path
    Map.Entry<String, File> mostSpecific = null;
    for (Map.Entry<String, File> root : mRoots.entrySet()) {
        final String rootPath = root.getValue().getPath(); // rootPath: "/storage/emulated/0"
        if (path.startsWith(rootPath) && (mostSpecific == null // path: "/data/data/com.example.android.development/cache/images/background.png"
                || rootPath.length() > mostSpecific.getValue().getPath().length())) {
            mostSpecific = root; // mostSpecific: null
        }
    }

    if (mostSpecific == null) {
        throw new IllegalArgumentException(
                "Failed to find configured root that contains " + path);
    }

    // Start at first char of path under root
    final String rootPath = mostSpecific.getValue().getPath();
    if (rootPath.endsWith("/")) {
        path = path.substring(rootPath.length());
    } else {
        path = path.substring(rootPath.length() + 1);
    }

    // Encode the tag and path separately
    path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
    return new Uri.Builder().scheme("content")
            .authority(mAuthority).encodedPath(path).build();
}

Things I have already tried:

  1. Add <root-path name="root" path="." /> to <paths>
  2. Try filesDir instead of cacheDir
  3. Run the app on a phone instead of an emulator

Solution

  • Your project has multiple resources named res/xml/provider_paths.xml, and you are winding up with one that is for some other FileProvider than yours.

    For example, you might have an app/ module and a something/ library module that app/ depends upon. If they both have res/xml/provider_paths.xml, then IIRC app/ "wins", and its resource gets included.

    The simplest solution is to rename your resource to be something else (e.g., res/xml/images_provider_paths.xml) and update your <provider> manifest entry to point to the new name.