MuntashirAkon/AppManager · error · java.io.IOException

Unable to open URI

Error message

Unable to open URI

What it means

In the key-store export flow, the dialog opens the user-picked destination URI via ContentResolver.openOutputStream. If the resolver returns null (no provider can write to that URI), an IOException 'Unable to open URI' is thrown and surfaced to the user as an error toast.

Solutions

  1. Pick a local destination (Downloads/On-device storage) instead of a cloud provider for the export
  2. Re-run the picker and select a fresh, writable location rather than reusing an old URI
  3. Check result code and extras in onActivityResult; ensure the URI has FLAG_GRANT_READ/WRITE permissions
  4. Catch the IOException and show a retry prompt instead of silently failing

Example fix

// before
OutputStream os = mActivity.getContentResolver().openOutputStream(uri);
// after
OutputStream os = mActivity.getContentResolver().openOutputStream(uri, "wt");
if (os == null) {
    UIUtils.displayShortToast(R.string.failed);
    return;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before export, verify the URI is writable
String type = getContentResolver().getType(uri);
if (type == null) { /* provider unknown; re-pick destination */ }

Try / catch

try (OutputStream os = getContentResolver().openOutputStream(uri)) {
    if (os == null) { /* show 'cannot write here' and re-pick */ return; }
    IoUtils.copy(is, os);
} catch (IOException e) {
    UIUtils.displayShortToast(R.string.failed);
}

Prevention

When it happens

Trigger: Exporting the AM keystore with ACTION_CREATE_DOCUMENT when the selected target's provider returns a null OutputStream, e.g. an unexportable cloud account or a non-writable location.

Common situations: Choosing a cloud-storage document (Drive, etc.) that only grants read access, picking a location in a revoked/removed storage provider, or a stale URI from a previous session.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


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

Appendix: source

Thrown at app/src/main/java/io/github/muntashirakon/AppManager/settings/crypto/ImportExportKeyStoreDialogFragment.java:50

import io.github.muntashirakon.AppManager.utils.ExUtils;
import io.github.muntashirakon.AppManager.utils.ThreadUtils;
import io.github.muntashirakon.AppManager.utils.UIUtils;
import io.github.muntashirakon.io.IoUtils;

public class ImportExportKeyStoreDialogFragment extends DialogFragment {
    public static final String TAG = "IEKeyStoreDialogFragment";

    private FragmentActivity mActivity;
    private final ActivityResultLauncher<String> mExportKeyStore = registerForActivityResult(
            new ActivityResultContracts.CreateDocument("application/octet-stream"), uri -> {
                if (uri == null) {
                    dismiss();
                    return;
                }
                ThreadUtils.postOnBackgroundThread(() -> {
                    try (InputStream is = new FileInputStream(AM_KEYSTORE_FILE);
                         OutputStream os = mActivity.getContentResolver().openOutputStream(uri)) {
                        if (os == null) throw new IOException("Unable to open URI");
                        IoUtils.copy(is, os);
                        ThreadUtils.postOnMainThread(() -> {
                            UIUtils.displayShortToast(R.string.done);
                            ExUtils.exceptionAsIgnored(this::dismiss);
                        });
                    } catch (IOException e) {
                        ThreadUtils.postOnMainThread(() -> {
                            UIUtils.displayShortToast(R.string.failed);
                            ExUtils.exceptionAsIgnored(this::dismiss);
                        });
                    }
                });
            });
    private final ActivityResultLauncher<String> mImportKeyStore = registerForActivityResult(
            new ActivityResultContracts.GetContent(), uri -> {
                if (uri == null) {
                    dismiss();
                    return;

View on GitHub (pinned to 0152f468fc)