Loading api/current.txt +4 −1 Original line number Diff line number Diff line Loading @@ -11405,7 +11405,8 @@ package android.content.pm { public class PackageInstaller { method public void abandonSession(int); method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException; method @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession(); method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession(); method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions(); method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getAllSessions(); method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getMySessions(); method @Nullable public android.content.pm.PackageInstaller.SessionInfo getSessionInfo(int); Loading Loading @@ -11490,11 +11491,13 @@ package android.content.pm { method @NonNull public String getStagedSessionErrorMessage(); method public long getUpdatedMillis(); method @NonNull public android.os.UserHandle getUser(); method public boolean hasParentSessionId(); method public boolean isActive(); method public boolean isCommitted(); method public boolean isMultiPackage(); method public boolean isSealed(); method public boolean isStaged(); method public boolean isStagedSessionActive(); method public boolean isStagedSessionApplied(); method public boolean isStagedSessionFailed(); method public boolean isStagedSessionReady(); core/java/android/content/pm/PackageInstaller.java +52 −26 Original line number Diff line number Diff line Loading @@ -68,6 +68,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; import java.util.stream.Collectors; /** * Offers the ability to install, upgrade, and remove applications on the Loading Loading @@ -486,35 +487,30 @@ public class PackageInstaller { } /** * Returns an active staged session, or {@code null} if there is none. * Returns first active staged session, or {@code null} if there is none. * * <p>Staged session is active iff: * <ul> * <li>It is committed, i.e. {@link SessionInfo#isCommitted()} is {@code true}, and * <li>it is not applied, i.e. {@link SessionInfo#isStagedSessionApplied()} is {@code * false}, and * <li>it is not failed, i.e. {@link SessionInfo#isStagedSessionFailed()} is {@code false}. * </ul> * <p>For more information on what sessions are considered active see * {@link SessionInfo#isStagedSessionActive()}. * * <p>In case of a multi-apk session, reasoning above is applied to the parent session, since * that is the one that should been {@link Session#commit committed}. * @deprecated Use {@link #getActiveStagedSessions} as there can be more than one active staged * session */ @Deprecated public @Nullable SessionInfo getActiveStagedSession() { final List<SessionInfo> stagedSessions = getStagedSessions(); for (SessionInfo s : stagedSessions) { if (s.isStagedSessionApplied() || s.isStagedSessionFailed()) { // Finalized session. continue; } if (s.getParentSessionId() != SessionInfo.INVALID_ID) { // Child session. continue; List<SessionInfo> activeSessions = getActiveStagedSessions(); return activeSessions.isEmpty() ? null : activeSessions.get(0); } if (s.isCommitted()) { return s; } } return null; /** * Returns list of active staged sessions. Returns empty list if there is none. * * <p>For more information on what sessions are considered active see * * {@link SessionInfo#isStagedSessionActive()}. */ public @NonNull List<SessionInfo> getActiveStagedSessions() { return getStagedSessions().stream() .filter(s -> s.isStagedSessionActive()) .collect(Collectors.toList()); } /** Loading Loading @@ -2234,12 +2230,35 @@ public class PackageInstaller { } /** * Returns true if this session is a staged session which will be applied at next reboot. * Returns true if this session is a staged session. */ public boolean isStaged() { return isStaged; } /** * Returns {@code true} if this session is an active staged session. * * We consider a session active if it has been committed and it is either pending * verification, or will be applied at next reboot. * * <p>Staged session is active iff: * <ul> * <li>It is committed, i.e. {@link SessionInfo#isCommitted()} is {@code true}, and * <li>it is not applied, i.e. {@link SessionInfo#isStagedSessionApplied()} is {@code * false}, and * <li>it is not failed, i.e. {@link SessionInfo#isStagedSessionFailed()} is * {@code false}. * </ul> * * <p>In case of a multi-package session, reasoning above is applied to the parent session, * since that is the one that should have been {@link Session#commit committed}. */ public boolean isStagedSessionActive() { return isStaged && isCommitted && !isStagedSessionApplied && !isStagedSessionFailed && !hasParentSessionId(); } /** * Returns the parent multi-package session ID if this session belongs to one, * {@link #INVALID_ID} otherwise. Loading @@ -2248,6 +2267,13 @@ public class PackageInstaller { return parentSessionId; } /** * Returns true if session has a valid parent session, otherwise false. */ public boolean hasParentSessionId() { return parentSessionId != INVALID_ID; } /** * Returns the set of session IDs that will be committed when this session is commited if * this session is a multi-package session. Loading core/java/android/content/pm/PackageManager.java +1 −1 Original line number Diff line number Diff line Loading @@ -1424,7 +1424,7 @@ public abstract class PackageManager { /** * Installation failed return code: a new staged session was attempted to be committed while * there is already one in-progress. * there is already one in-progress or new session has package that is already staged. * * @hide */ Loading services/core/java/com/android/server/pm/PackageInstallerSession.java +30 −15 Original line number Diff line number Diff line Loading @@ -1090,19 +1090,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { assertMultiPackageConsistencyLocked(childSessions); } if (params.isStaged) { final PackageInstallerSession activeSession = mStagingManager.getActiveSession(); final boolean anotherSessionAlreadyInProgress = activeSession != null && sessionId != activeSession.sessionId && mParentSessionId != activeSession.sessionId; if (anotherSessionAlreadyInProgress) { throw new PackageManagerException( PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, "There is already in-progress committed staged session " + activeSession.sessionId, null); } } // Read transfers from the original owner stay open, but as the session's data // cannot be modified anymore, there is no leak of information. For staged sessions, // further validation is performed by the staging manager. Loading @@ -1125,6 +1112,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new PackageManagerException(e); } } if (params.isStaged) { mStagingManager.checkNonOverlappingWithStagedSessions(this); } } catch (PackageManagerException e) { // Session is sealed but could not be verified, we need to destroy it. destroyInternal(); Loading Loading @@ -1449,7 +1440,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** * Validate apex install. * <p> * Sets {@link #mResolvedBaseFile} for RollbackManager to use. * Sets {@link #mResolvedBaseFile} for RollbackManager to use. Sets {@link #mPackageName} for * StagingManager to use. */ @GuardedBy("mLock") private void validateApexInstallLocked() Loading Loading @@ -1478,8 +1470,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final File targetFile = new File(stageDir, targetName); resolveAndStageFile(addedFile, targetFile); mResolvedBaseFile = targetFile; // Populate package name of the apex session mPackageName = null; final ApkLite apk; try { apk = PackageParser.parseApkLite( mResolvedBaseFile, PackageParser.PARSE_COLLECT_CERTIFICATES); } catch (PackageParserException e) { throw PackageManagerException.from(e); } if (mPackageName == null) { mPackageName = apk.packageName; mVersionCode = apk.getLongVersionCode(); } } /** Loading Loading @@ -1847,6 +1853,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } /** * @return the package name of this session */ String getPackageName() { synchronized (mLock) { return mPackageName; } } /** * @return the timestamp of when this session last changed state */ Loading services/core/java/com/android/server/pm/StagingManager.java +82 −11 Original line number Diff line number Diff line Loading @@ -539,25 +539,95 @@ public class StagingManager { mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId); } @Nullable PackageInstallerSession getActiveSession() { /** * <p> Check if the session provided is non-overlapping with the active staged sessions. * * <p> A session is non-overlapping if it meets one of the following conditions: </p> * <ul> * <li>It is a parent session</li> * <li>It is already one of the active sessions</li> * <li>Its package name is not same as any of the active sessions</li> * </ul> * @throws PackageManagerException if session fails the check */ void checkNonOverlappingWithStagedSessions(@NonNull PackageInstallerSession session) throws PackageManagerException { if (session.isMultiPackage()) { // We cannot say a parent session overlaps until we process its children return; } if (session.getPackageName() == null) { throw new PackageManagerException(PackageManager.INSTALL_FAILED_INVALID_APK, "Cannot stage session " + session.sessionId + " with package name null"); } synchronized (mStagedSessions) { for (int i = 0; i < mStagedSessions.size(); i++) { final PackageInstallerSession session = mStagedSessions.valueAt(i); if (!session.isCommitted()) { final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i); if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()) { continue; } if (session.hasParentSessionId()) { // Staging manager will finalize only parent session. Ignore child sessions // picking the active. if (stagedSession.isMultiPackage()) { // This active parent staged session is useless as it doesn't have a package // name and the session we are checking is not a parent session either. continue; } if (!session.isStagedSessionApplied() && !session.isStagedSessionFailed()) { return session; // From here on, stagedSession is a non-parent active staged session // Check if stagedSession has an active parent session or not if (stagedSession.hasParentSessionId()) { int parentId = stagedSession.getParentSessionId(); PackageInstallerSession parentSession = mStagedSessions.get(parentId); if (parentSession == null || parentSession.isStagedAndInTerminalState()) { // Parent session has been abandoned or terminated already continue; } } // Check if session is one of the active sessions if (session.sessionId == stagedSession.sessionId) { Slog.w(TAG, "Session " + session.sessionId + " is already staged"); continue; } // If session is not among the active sessions, then it cannot have same package // name as any of the active sessions. if (session.getPackageName().equals(stagedSession.getPackageName())) { throw new PackageManagerException( PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, "Package: " + session.getPackageName() + " in session: " + session.sessionId + " has been staged already by session: " + stagedSession.sessionId, null); } // TODO(b/141843321): Add support for staging multiple sessions in apexd // Since apexd doesn't support multiple staged sessions yet, we have to careful how // we handle apex sessions. We want to allow a set of apex sessions under the same // parent to be staged when there is no previously staged apex sessions. if (isApexSession(session) && isApexSession(stagedSession)) { // session is apex and it can co-exist with stagedSession only if they are from // same parent final boolean coExist; if (!session.hasParentSessionId() && !stagedSession.hasParentSessionId()) { // Both single package apex sessions. Cannot co-exist. coExist = false; } else { // At least one of the session has parent. Both must be from same parent. coExist = session.getParentSessionId() == stagedSession.getParentSessionId(); } if (!coExist) { throw new PackageManagerException( PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, "Package: " + session.getPackageName() + " in session: " + session.sessionId + " cannot be staged as there is " + "already another apex staged session: " + stagedSession.sessionId, null); } } } } return null; } void createSession(@NonNull PackageInstallerSession sessionInfo) { Loading @@ -584,7 +654,8 @@ public class StagingManager { ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId); if (apexSession == null || isApexSessionFinalized(apexSession)) { Slog.w(TAG, "Cannot abort session because it is not active or APEXD is not reachable"); "Cannot abort session " + session.sessionId + " because it is not active or APEXD is not reachable"); return; } mApexManager.abortActiveSession(); Loading Loading
api/current.txt +4 −1 Original line number Diff line number Diff line Loading @@ -11405,7 +11405,8 @@ package android.content.pm { public class PackageInstaller { method public void abandonSession(int); method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException; method @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession(); method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession(); method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions(); method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getAllSessions(); method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getMySessions(); method @Nullable public android.content.pm.PackageInstaller.SessionInfo getSessionInfo(int); Loading Loading @@ -11490,11 +11491,13 @@ package android.content.pm { method @NonNull public String getStagedSessionErrorMessage(); method public long getUpdatedMillis(); method @NonNull public android.os.UserHandle getUser(); method public boolean hasParentSessionId(); method public boolean isActive(); method public boolean isCommitted(); method public boolean isMultiPackage(); method public boolean isSealed(); method public boolean isStaged(); method public boolean isStagedSessionActive(); method public boolean isStagedSessionApplied(); method public boolean isStagedSessionFailed(); method public boolean isStagedSessionReady();
core/java/android/content/pm/PackageInstaller.java +52 −26 Original line number Diff line number Diff line Loading @@ -68,6 +68,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; import java.util.stream.Collectors; /** * Offers the ability to install, upgrade, and remove applications on the Loading Loading @@ -486,35 +487,30 @@ public class PackageInstaller { } /** * Returns an active staged session, or {@code null} if there is none. * Returns first active staged session, or {@code null} if there is none. * * <p>Staged session is active iff: * <ul> * <li>It is committed, i.e. {@link SessionInfo#isCommitted()} is {@code true}, and * <li>it is not applied, i.e. {@link SessionInfo#isStagedSessionApplied()} is {@code * false}, and * <li>it is not failed, i.e. {@link SessionInfo#isStagedSessionFailed()} is {@code false}. * </ul> * <p>For more information on what sessions are considered active see * {@link SessionInfo#isStagedSessionActive()}. * * <p>In case of a multi-apk session, reasoning above is applied to the parent session, since * that is the one that should been {@link Session#commit committed}. * @deprecated Use {@link #getActiveStagedSessions} as there can be more than one active staged * session */ @Deprecated public @Nullable SessionInfo getActiveStagedSession() { final List<SessionInfo> stagedSessions = getStagedSessions(); for (SessionInfo s : stagedSessions) { if (s.isStagedSessionApplied() || s.isStagedSessionFailed()) { // Finalized session. continue; } if (s.getParentSessionId() != SessionInfo.INVALID_ID) { // Child session. continue; List<SessionInfo> activeSessions = getActiveStagedSessions(); return activeSessions.isEmpty() ? null : activeSessions.get(0); } if (s.isCommitted()) { return s; } } return null; /** * Returns list of active staged sessions. Returns empty list if there is none. * * <p>For more information on what sessions are considered active see * * {@link SessionInfo#isStagedSessionActive()}. */ public @NonNull List<SessionInfo> getActiveStagedSessions() { return getStagedSessions().stream() .filter(s -> s.isStagedSessionActive()) .collect(Collectors.toList()); } /** Loading Loading @@ -2234,12 +2230,35 @@ public class PackageInstaller { } /** * Returns true if this session is a staged session which will be applied at next reboot. * Returns true if this session is a staged session. */ public boolean isStaged() { return isStaged; } /** * Returns {@code true} if this session is an active staged session. * * We consider a session active if it has been committed and it is either pending * verification, or will be applied at next reboot. * * <p>Staged session is active iff: * <ul> * <li>It is committed, i.e. {@link SessionInfo#isCommitted()} is {@code true}, and * <li>it is not applied, i.e. {@link SessionInfo#isStagedSessionApplied()} is {@code * false}, and * <li>it is not failed, i.e. {@link SessionInfo#isStagedSessionFailed()} is * {@code false}. * </ul> * * <p>In case of a multi-package session, reasoning above is applied to the parent session, * since that is the one that should have been {@link Session#commit committed}. */ public boolean isStagedSessionActive() { return isStaged && isCommitted && !isStagedSessionApplied && !isStagedSessionFailed && !hasParentSessionId(); } /** * Returns the parent multi-package session ID if this session belongs to one, * {@link #INVALID_ID} otherwise. Loading @@ -2248,6 +2267,13 @@ public class PackageInstaller { return parentSessionId; } /** * Returns true if session has a valid parent session, otherwise false. */ public boolean hasParentSessionId() { return parentSessionId != INVALID_ID; } /** * Returns the set of session IDs that will be committed when this session is commited if * this session is a multi-package session. Loading
core/java/android/content/pm/PackageManager.java +1 −1 Original line number Diff line number Diff line Loading @@ -1424,7 +1424,7 @@ public abstract class PackageManager { /** * Installation failed return code: a new staged session was attempted to be committed while * there is already one in-progress. * there is already one in-progress or new session has package that is already staged. * * @hide */ Loading
services/core/java/com/android/server/pm/PackageInstallerSession.java +30 −15 Original line number Diff line number Diff line Loading @@ -1090,19 +1090,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { assertMultiPackageConsistencyLocked(childSessions); } if (params.isStaged) { final PackageInstallerSession activeSession = mStagingManager.getActiveSession(); final boolean anotherSessionAlreadyInProgress = activeSession != null && sessionId != activeSession.sessionId && mParentSessionId != activeSession.sessionId; if (anotherSessionAlreadyInProgress) { throw new PackageManagerException( PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, "There is already in-progress committed staged session " + activeSession.sessionId, null); } } // Read transfers from the original owner stay open, but as the session's data // cannot be modified anymore, there is no leak of information. For staged sessions, // further validation is performed by the staging manager. Loading @@ -1125,6 +1112,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new PackageManagerException(e); } } if (params.isStaged) { mStagingManager.checkNonOverlappingWithStagedSessions(this); } } catch (PackageManagerException e) { // Session is sealed but could not be verified, we need to destroy it. destroyInternal(); Loading Loading @@ -1449,7 +1440,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** * Validate apex install. * <p> * Sets {@link #mResolvedBaseFile} for RollbackManager to use. * Sets {@link #mResolvedBaseFile} for RollbackManager to use. Sets {@link #mPackageName} for * StagingManager to use. */ @GuardedBy("mLock") private void validateApexInstallLocked() Loading Loading @@ -1478,8 +1470,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final File targetFile = new File(stageDir, targetName); resolveAndStageFile(addedFile, targetFile); mResolvedBaseFile = targetFile; // Populate package name of the apex session mPackageName = null; final ApkLite apk; try { apk = PackageParser.parseApkLite( mResolvedBaseFile, PackageParser.PARSE_COLLECT_CERTIFICATES); } catch (PackageParserException e) { throw PackageManagerException.from(e); } if (mPackageName == null) { mPackageName = apk.packageName; mVersionCode = apk.getLongVersionCode(); } } /** Loading Loading @@ -1847,6 +1853,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } /** * @return the package name of this session */ String getPackageName() { synchronized (mLock) { return mPackageName; } } /** * @return the timestamp of when this session last changed state */ Loading
services/core/java/com/android/server/pm/StagingManager.java +82 −11 Original line number Diff line number Diff line Loading @@ -539,25 +539,95 @@ public class StagingManager { mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId); } @Nullable PackageInstallerSession getActiveSession() { /** * <p> Check if the session provided is non-overlapping with the active staged sessions. * * <p> A session is non-overlapping if it meets one of the following conditions: </p> * <ul> * <li>It is a parent session</li> * <li>It is already one of the active sessions</li> * <li>Its package name is not same as any of the active sessions</li> * </ul> * @throws PackageManagerException if session fails the check */ void checkNonOverlappingWithStagedSessions(@NonNull PackageInstallerSession session) throws PackageManagerException { if (session.isMultiPackage()) { // We cannot say a parent session overlaps until we process its children return; } if (session.getPackageName() == null) { throw new PackageManagerException(PackageManager.INSTALL_FAILED_INVALID_APK, "Cannot stage session " + session.sessionId + " with package name null"); } synchronized (mStagedSessions) { for (int i = 0; i < mStagedSessions.size(); i++) { final PackageInstallerSession session = mStagedSessions.valueAt(i); if (!session.isCommitted()) { final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i); if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()) { continue; } if (session.hasParentSessionId()) { // Staging manager will finalize only parent session. Ignore child sessions // picking the active. if (stagedSession.isMultiPackage()) { // This active parent staged session is useless as it doesn't have a package // name and the session we are checking is not a parent session either. continue; } if (!session.isStagedSessionApplied() && !session.isStagedSessionFailed()) { return session; // From here on, stagedSession is a non-parent active staged session // Check if stagedSession has an active parent session or not if (stagedSession.hasParentSessionId()) { int parentId = stagedSession.getParentSessionId(); PackageInstallerSession parentSession = mStagedSessions.get(parentId); if (parentSession == null || parentSession.isStagedAndInTerminalState()) { // Parent session has been abandoned or terminated already continue; } } // Check if session is one of the active sessions if (session.sessionId == stagedSession.sessionId) { Slog.w(TAG, "Session " + session.sessionId + " is already staged"); continue; } // If session is not among the active sessions, then it cannot have same package // name as any of the active sessions. if (session.getPackageName().equals(stagedSession.getPackageName())) { throw new PackageManagerException( PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, "Package: " + session.getPackageName() + " in session: " + session.sessionId + " has been staged already by session: " + stagedSession.sessionId, null); } // TODO(b/141843321): Add support for staging multiple sessions in apexd // Since apexd doesn't support multiple staged sessions yet, we have to careful how // we handle apex sessions. We want to allow a set of apex sessions under the same // parent to be staged when there is no previously staged apex sessions. if (isApexSession(session) && isApexSession(stagedSession)) { // session is apex and it can co-exist with stagedSession only if they are from // same parent final boolean coExist; if (!session.hasParentSessionId() && !stagedSession.hasParentSessionId()) { // Both single package apex sessions. Cannot co-exist. coExist = false; } else { // At least one of the session has parent. Both must be from same parent. coExist = session.getParentSessionId() == stagedSession.getParentSessionId(); } if (!coExist) { throw new PackageManagerException( PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, "Package: " + session.getPackageName() + " in session: " + session.sessionId + " cannot be staged as there is " + "already another apex staged session: " + stagedSession.sessionId, null); } } } } return null; } void createSession(@NonNull PackageInstallerSession sessionInfo) { Loading @@ -584,7 +654,8 @@ public class StagingManager { ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId); if (apexSession == null || isApexSessionFinalized(apexSession)) { Slog.w(TAG, "Cannot abort session because it is not active or APEXD is not reachable"); "Cannot abort session " + session.sessionId + " because it is not active or APEXD is not reachable"); return; } mApexManager.abortActiveSession(); Loading