asLody/VirtualApp · error · java.lang.SecurityException

Files still open

Error message

Files still open

What it means

commit() seals the session, but first requires that every active FileBridge (the write stream bridge for openWrite) is closed. If any writer still holds an open bridge, it throws SecurityException('Files still open') and refuses to seal, protecting against installing a partially written APK.

Solutions

  1. Close every OutputStream returned by openWrite() (use try-with-resources) before commit().
  2. On failure paths, close streams in a finally block or call session.abandon() and start over.
  3. Await/join all background copy tasks before invoking commit().

Example fix

// before
OutputStream out = session.openWrite("base.apk", 0, -1);
out.write(apkBytes);
session.commit(callback); // SecurityException: Files still open

// after
try (OutputStream out = session.openWrite("base.apk", 0, -1)) {
    out.write(apkBytes);
    out.flush();
}
session.commit(callback);
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure all openWrite streams closed before commit
Set<OutputStream> openStreams; // maintained by your wrapper
if (!openStreams.isEmpty()) {
    openStreams.forEach(IOUtils::closeQuietly);
}

Try / catch

try {
    session.commit(statusReceiver);
} catch (SecurityException e) {
    if (String.valueOf(e.getMessage()).contains("Files still open")) {
        closeAllStreams();
        session.abandon(); // or re-commit after closing if still valid
    } else throw e;
}

Prevention

When it happens

Trigger: Calling session.commit() while an OutputStream from openWrite() is still open (not closed/flushed); leaking a stream on an exception path; another thread still mid-write.

Common situations: Exception during APK copy leaves stream unclosed, later commit fails; writing multiple splits and forgetting to close one; asynchronous copy tasks not joined before commit.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of asLody/VirtualApp@666fefcb5d (2026-09-09). Data as JSON: /api/errors/33998c11abadc8a2. Report an issue: GitHub.

Appendix: source

Thrown at VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/installer/PackageInstallerSession.java:392

    }

    @Override
    public void close() throws RemoteException {
        if (mActiveCount.decrementAndGet() == 0) {
            mCallback.onSessionActiveChanged(this, false);
        }
    }

    @Override
    public void commit(IntentSender statusReceiver) throws RemoteException {
        final boolean wasSealed;
        synchronized (mLock) {
            wasSealed = mSealed;
            if (!mSealed) {
                // Verify that all writers are hands-off
                for (FileBridge bridge : mBridges) {
                    if (!bridge.isClosed()) {
                        throw new SecurityException("Files still open");
                    }
                }
                mSealed = true;
            }

            // Client staging is fully done at this point
            mClientProgress = 1f;
            computeProgressLocked(true);
        }

        if (!wasSealed) {
            // Persist the fact that we've sealed ourselves to prevent
            // mutations of any hard links we create. We do this without holding
            // the session lock, since otherwise it's a lock inversion.
            mCallback.onSessionSealedBlocking(this);
        }

        // This ongoing commit should keep session active, even though client

View on GitHub (pinned to 666fefcb5d)