Merge "Allow staging multiple session with non-overlapping packages (apk-only)"

This commit is contained in:
Treehugger Robot
2020-03-24 16:53:39 +00:00
committed by Gerrit Code Review
5 changed files with 169 additions and 54 deletions

View File

@@ -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);
@@ -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();

View File

@@ -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
@@ -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;
}
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());
}
/**
@@ -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.
@@ -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.

View File

@@ -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
*/

View File

@@ -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.
@@ -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();
@@ -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()
@@ -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();
}
}
/**
@@ -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
*/

View File

@@ -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) {
@@ -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();