Merge "Allow staging multiple session with non-overlapping packages (apk-only)"
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user