MuntashirAkon/AppManager · error · FileNotFoundException

Shared storage unavailable.

Error message

Shared storage unavailable.

What it means

getBestExternalDataSubdir scans the device's array of app-specific external storage directories and returns the first usable one. It throws FileNotFoundException("Shared storage unavailable.") when the extDirs array itself is null, meaning the OS reported no external/shared storage at all (Context#getExternalFilesDirs returned null). The method never returns null; callers like getExternalCachePath propagate this failure.

Source

Thrown at app/src/main/java/io/github/muntashirakon/AppManager/utils/FileUtils.java:187

        Context context = ContextUtils.getContext();
        try {
            return getExternalCachePath(context);
        } catch (FileNotFoundException e) {
            return context.getCacheDir();
        }
    }

    @AnyThread
    @NonNull
    public static File getExternalCachePath(@NonNull Context context) throws FileNotFoundException {
        return getBestExternalDataSubdir(context.getExternalCacheDirs());
    }

    @AnyThread
    @NonNull
    public static File getBestExternalDataSubdir(@Nullable File[] extDirs) throws FileNotFoundException {
        if (extDirs == null) {
            throw new FileNotFoundException("Shared storage unavailable.");
        }
        String lastReason = null;
        for (File extDir : extDirs) {
            // The priority is from top to bottom of the list as per Context#getExternalDir()
            if (extDir == null) {
                // Other external directory might exist
                continue;
            }
            if (!(extDir.exists() || extDir.mkdirs())) {
                // Try to recreate this with root
                if (RunnerUtils.isRootGiven() && forceCreateExternalDataSubDir(extDir)) {
                    Log.i(TAG, "Root created %s", extDir);
                    return extDir;
                }
                lastReason = extDir + ": permission denied.";
                Log.w(TAG, "Could not use %s.", extDir);
                continue;
            }

View on GitHub (pinned to 0152f468fc)

Solutions

  1. Check extDirs != null (or catch FileNotFoundException) before relying on external storage and fall back to internal Context#getCacheDir/getFilesDir
  2. Verify external storage is mounted via Environment.getExternalStorageState() before calling
  3. Avoid calling storage APIs before the device finishes booting (check Environment.getExternalStorageState / boot-completed)
  4. On restricted profiles, grant or verify storage access for the calling user

Example fix

// before
File dir = FileUtils.getBestExternalDataSubdir(extDirs);
// after
File dir;
try {
    dir = FileUtils.getBestExternalDataSubdir(extDirs);
} catch (FileNotFoundException e) {
    dir = context.getCacheDir(); // fall back to internal storage
}
Defensive patterns

Strategy: fallback

Validate before calling

File[] extDirs = context.getExternalFilesDirs(null);
if (extDirs == null || Arrays.stream(extDirs).filter(Objects::nonNull).findAny().isEmpty()) {
    // use internal storage path
}

Type guard

boolean hasExternalStorage(File[] extDirs) {
    return extDirs != null && extDirs.length > 0 && Arrays.stream(extDirs).anyMatch(Objects::nonNull);
}

Try / catch

try {
    dir = FileUtils.getBestExternalDataSubdir(extDirs);
} catch (FileNotFoundException e) {
    dir = context.getCacheDir();
}

Prevention

When it happens

Trigger: Calling getExternalCachePath/getBestExternalDataSubdir on a device where Context#getExternalFilesDirs(String, String) returns null — typically because no external/shared volume is mounted or accessible to the app (rare on modern Android; occurs with inaccessible storage profiles, restricted storage permissions, or unusual builds).

Common situations: Devices with emulated storage removed or unmounted; work-profile/secondary-user setups where external dirs are disabled; custom ROMs or emulators with broken storage emulation; running very early in the boot cycle before volumes are scanned.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of MuntashirAkon/AppManager@0152f468fc (2026-09-12). Data as JSON: /api/errors/10691666622316dc. Report an issue: GitHub.