diff options
| -rw-r--r-- | api/current.txt | 5 | ||||
| -rw-r--r-- | core/java/android/content/pm/PackageInstaller.java | 78 | ||||
| -rw-r--r-- | core/java/android/content/pm/PackageManager.java | 2 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/PackageInstallerSession.java | 45 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/StagingManager.java | 93 |
5 files changed, 169 insertions, 54 deletions
diff --git a/api/current.txt b/api/current.txt index b1423b485ae3..c7b6c2ec2645 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11509,7 +11509,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); @@ -11594,11 +11595,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(); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index edc66c58b197..0c529798622e 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -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 @@ -479,35 +480,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; - } - if (s.isCommitted()) { - return s; - } - } - return null; + List<SessionInfo> activeSessions = getActiveStagedSessions(); + return activeSessions.isEmpty() ? null : activeSessions.get(0); + } + + /** + * 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()); } /** @@ -2227,13 +2223,36 @@ 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. */ @@ -2242,6 +2261,13 @@ public class PackageInstaller { } /** + * 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. */ diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index c56c3076c3be..bbfdf910a9da 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1435,7 +1435,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 */ diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index d5f4ff236b24..41c323c760f6 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1095,19 +1095,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. @@ -1130,6 +1117,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(); @@ -1453,7 +1444,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() @@ -1482,8 +1474,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(); + } } /** @@ -1852,6 +1858,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 */ public long getUpdatedMillis() { diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 6b4ef698a8f4..532ff86e9014 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -546,25 +546,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; + } + + // 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.isStagedSessionApplied() && !session.isStagedSessionFailed()) { - return session; + + // 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) { @@ -591,7 +661,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(); |