summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt5
-rw-r--r--core/java/android/content/pm/PackageInstaller.java78
-rw-r--r--core/java/android/content/pm/PackageManager.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java45
-rw-r--r--services/core/java/com/android/server/pm/StagingManager.java93
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();