{"record":{"id":"2577a38a39900989","repo":"asLody/VirtualApp","slug":"must-be-sealed-to-accept-permissions","errorCode":null,"errorMessage":"Must be sealed to accept permissions","messagePattern":"Must be sealed to accept permissions","errorType":"exception","errorClass":"java.lang.SecurityException","httpStatus":null,"severity":"error","filePath":"VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/installer/PackageInstallerSession.java","lineNumber":458,"sourceCode":"\n    private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {\n        mFinalStatus = returnCode;\n        mFinalMessage = msg;\n\n        if (mRemoteObserver != null) {\n            try {\n                mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras);\n            } catch (RemoteException ignored) {\n            }\n        }\n\n        final boolean success = (returnCode == INSTALL_SUCCEEDED);\n        mCallback.onSessionFinished(this, success);\n    }\n\n    void setPermissionsResult(boolean accepted) {\n        if (!mSealed) {\n            throw new SecurityException(\"Must be sealed to accept permissions\");\n        }\n\n        if (accepted) {\n            // Mark and kick off another install pass\n            synchronized (mLock) {\n                mPermissionsAccepted = true;\n            }\n            mHandler.obtainMessage(MSG_COMMIT).sendToTarget();\n        } else {\n            destroyInternal();\n            dispatchSessionFinished(INSTALL_FAILED_ABORTED, \"User rejected permissions\", null);\n        }\n    }\n\n    public void open() throws IOException {\n        if (mActiveCount.getAndIncrement() == 0) {\n            mCallback.onSessionActiveChanged(this, true);\n        }","sourceCodeStart":440,"sourceCodeEnd":476,"githubUrl":"https://github.com/asLody/VirtualApp/blob/666fefcb5d3f39cc944001c3457c38ffd6544c87/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/installer/PackageInstallerSession.java#L440-L476","documentation":"When an install requires runtime-permission confirmation, the system may ask the user; setPermissionsResult(accepted) records the answer and kicks off the next install pass, but only if the session is already sealed (committed). Calling it before commit() throws SecurityException('Must be sealed to accept permissions').","triggerScenarios":"Delivering the permission-accept/deny callback for a session that has not gone through commit(); invoking setPermissionsResult manually out of order or twice across unsealed sessions.","commonSituations":"Custom installer UI that responds to a permission prompt for a session which was concurrently abandoned or never committed; race between commit() and the user's permission answer; wrong sessionId passed to the callback.","solutions":["Ensure commit() was called and completed (mSealed true) before relaying the permission decision.","Verify the sessionId in the permission callback matches the committed session; ignore stale callbacks.","If the session was abandoned, drop the permission response instead of calling setPermissionsResult."],"exampleFix":"// before\nif (promptAccepted) session.setPermissionsResult(true); // SecurityException if unsealed\n\n// after\nif (promptAccepted && session.sealed) {\n    session.setPermissionsResult(true);\n} else {\n    Log.w(TAG, \"Ignoring permission result for unsealed/unknown session\");\n}","handlingStrategy":"try-catch","validationCode":"// Only forward results for sessions you committed yourself\nif (!isSessionCommitted(sessionId)) return; // drop stale permission callback","typeGuard":null,"tryCatchPattern":"try {\n    session.setPermissionsResult(accepted);\n} catch (SecurityException e) {\n    if (String.valueOf(e.getMessage()).contains(\"Must be sealed\")) {\n        Log.w(TAG, \"Permission result for unsealed session ignored\");\n    } else throw e;\n}","preventionTips":["Only relay permission results through the flow initiated by commit().","Match callback sessionId against your tracked committed sessions.","Ignore/discard results for abandoned sessions."],"tags":["security-exception","session-lifecycle","permissions","android"],"backgroundTag":"invalid-state-transition","analyzedSha":"666fefcb5d3f39cc944001c3457c38ffd6544c87","analyzedAt":"2026-09-09T11:09:01.694Z","contentChangedAt":"2026-09-09T11:09:01.694Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}