{"record":{"id":"526d73070dee9455","repo":"asLody/VirtualApp","slug":"exactly-one-of-stagedir-or-stagecid-stage-must-be","errorCode":null,"errorMessage":"Exactly one of stageDir or stageCid stage must be set","messagePattern":"Exactly one of stageDir or stageCid stage must be set","errorType":"exception","errorClass":"java.lang.IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/installer/PackageInstallerSession.java","lineNumber":481,"sourceCode":"            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        }\n\n        synchronized (mLock) {\n            if (!mPrepared) {\n                if (stageDir == null) {\n                    throw new IllegalArgumentException(\n                            \"Exactly one of stageDir or stageCid stage must be set\");\n                }\n                mPrepared = true;\n                mCallback.onSessionPrepared(this);\n            }\n        }\n    }\n\n\n    public static String getCompleteMessage(Throwable t) {\n        final StringBuilder builder = new StringBuilder();\n        builder.append(t.getMessage());\n        while ((t = t.getCause()) != null) {\n            builder.append(\": \").append(t.getMessage());\n        }\n        return builder.toString();\n    }\n","sourceCodeStart":463,"sourceCodeEnd":499,"githubUrl":"https://github.com/asLody/VirtualApp/blob/666fefcb5d3f39cc944001c3457c38ffd6544c87/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/installer/PackageInstallerSession.java#L463-L499","documentation":"A session's backing storage must be identified exactly once: either a stageDir (directory on disk) or a stageCid (encrypted-container ID). open() checks on first preparation that stageDir is non-null (stageCid is handled by the caller's context); if stageDir is null it throws IllegalArgumentException('Exactly one of stageDir or stageCid stage must be set').","triggerScenarios":"Calling session.open(null, userId) on a first-time (unprepared) session; constructing PackageInstallerSession with a null stage dir and then opening it; opening a session twice where the first open never set mPrepared due to a null arg.","commonSituations":"Custom code paths that build sessions without VEnvironment.getPackageInstallerStageDir()-derived directories; copy-paste from AOSP variants where stageCid was used but VirtualApp expects a stageDir; refactors dropping the stageDir argument.","solutions":["Pass a valid, writable stage directory (e.g. new File(VEnvironment.getPackageInstallerStageDir(), String.valueOf(sessionId))) to open().","Provide exactly one of stageDir or stageCid — never both null (and avoid both set).","Confirm the stage dir exists (mkdirs) and is accessible before open()."],"exampleFix":"// before\nsession.open(null, userId); // IllegalArgumentException\n\n// after\nFile stageDir = new File(VEnvironment.getPackageInstallerStageDir(), String.valueOf(session.sessionId));\nstageDir.mkdirs();\nsession.open(stageDir, userId);","handlingStrategy":"validation","validationCode":"File stageDir = new File(VEnvironment.getPackageInstallerStageDir(), String.valueOf(sessionId));\nif (stageDir == null || (!stageDir.exists() && !stageDir.mkdirs())) {\n    throw new IOException(\"Cannot prepare stage dir: \" + stageDir);\n}\nsession.open(stageDir, userId);","typeGuard":"boolean hasValidStage(File stageDir) { return stageDir != null && (stageDir.isDirectory() || stageDir.mkdirs()); }","tryCatchPattern":"try {\n    session.open(stageDir, userId);\n} catch (IllegalArgumentException e) {\n    if (String.valueOf(e.getMessage()).startsWith(\"Exactly one of stageDir\")) {\n        session.open(defaultStageDirFor(session.sessionId), userId);\n    } else throw e;\n}","preventionTips":["Never call open(null, ...); always derive stageDir from the session ID.","Create the stage directory before opening.","Keep stageDir/stageCid handling in a single helper."],"tags":["illegal-argument","missing-stage-dir","package-installer","android"],"backgroundTag":"missing-required-argument","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"}