Move some fields/methods into StagedSession (2/n)

These members are staged session exclusive and should be moved
into the class.

Bug: 166694095
Test: atest StagedInstallTest

Change-Id: I7e80c13f14a6fc70054ac79ac37857f1a7914e87
This commit is contained in:
JW Wang
2020-12-11 14:58:21 +08:00
parent 7cbf063fa9
commit 2cf104161f

View File

@@ -429,22 +429,45 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@GuardedBy("mLock")
private ArrayMap<String, List<CertifiedChecksum>> mChecksums = new ArrayMap<>();
@GuardedBy("mLock")
private boolean mStagedSessionApplied;
@GuardedBy("mLock")
private boolean mStagedSessionReady;
@GuardedBy("mLock")
private boolean mStagedSessionFailed;
@GuardedBy("mLock")
private int mStagedSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR;
@GuardedBy("mLock")
private String mStagedSessionErrorMessage;
@Nullable
final StagedSession mStagedSession;
@VisibleForTesting
public class StagedSession implements StagingManager.StagedSession {
@GuardedBy("mLock")
private boolean mSessionApplied;
@GuardedBy("mLock")
private boolean mSessionReady;
@GuardedBy("mLock")
private boolean mSessionFailed;
@GuardedBy("mLock")
private int mSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR;
@GuardedBy("mLock")
private String mSessionErrorMessage;
/**
* The callback to run when pre-reboot verification has ended. Used by {@link #abandon()}
* to delay session clean-up until it is safe to do so.
*/
@GuardedBy("mLock")
@Nullable
private Runnable mPendingAbandonCallback;
/**
* {@code true} if pre-reboot verification is ongoing which means it is not safe for
* {@link #abandon()} to clean up staging directories.
*/
@GuardedBy("mLock")
private boolean mInPreRebootVerification;
StagedSession(boolean isReady, boolean isApplied, boolean isFailed, int errorCode,
String errorMessage) {
mSessionReady = isReady;
mSessionApplied = isApplied;
mSessionFailed = isFailed;
mSessionErrorCode = errorCode;
mSessionErrorMessage = errorMessage != null ? errorMessage : "";
}
@Override
public List<StagingManager.StagedSession> getChildSessions() {
if (!params.isMultiPackage) {
@@ -482,7 +505,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public boolean containsApexSession() {
return PackageInstallerSession.this.containsApexSession();
return sessionContains((s) -> s.isApexSession());
}
@Override
@@ -492,17 +515,52 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public void setSessionReady() {
setStagedSessionReady();
synchronized (mLock) {
// Do not allow destroyed/failed staged session to change state
if (mDestroyed || mSessionFailed) return;
mSessionReady = true;
mSessionApplied = false;
mSessionFailed = false;
mSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR;
mSessionErrorMessage = "";
}
mCallback.onStagedSessionChanged(PackageInstallerSession.this);
}
@Override
public void setSessionFailed(int errorCode, String errorMessage) {
setStagedSessionFailed(errorCode, errorMessage);
List<PackageInstallerSession> childSessions;
synchronized (mLock) {
// Do not allow destroyed/failed staged session to change state
if (mDestroyed || mSessionFailed) return;
mSessionReady = false;
mSessionApplied = false;
mSessionFailed = true;
mSessionErrorCode = errorCode;
mSessionErrorMessage = errorMessage;
Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage);
childSessions = getChildSessionsLocked();
}
cleanStageDir(childSessions);
mCallback.onStagedSessionChanged(PackageInstallerSession.this);
}
@Override
public void setSessionApplied() {
setStagedSessionApplied();
List<PackageInstallerSession> childSessions;
synchronized (mLock) {
// Do not allow destroyed/failed staged session to change state
if (mDestroyed || mSessionFailed) return;
mSessionReady = false;
mSessionApplied = true;
mSessionFailed = false;
mSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR;
mSessionErrorMessage = "";
Slog.d(TAG, "Marking session " + sessionId + " as applied");
childSessions = getChildSessionsLocked();
}
cleanStageDir(childSessions);
mCallback.onStagedSessionChanged(PackageInstallerSession.this);
}
@Override
@@ -510,10 +568,35 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return PackageInstallerSession.this.containsApkSession();
}
/**
* Installs apks of staged session while skipping the verification process for a committed
* and ready session.
*/
@Override
public void installSession(IntentSender statusReceiver) {
installStagedSession(statusReceiver);
assertCallerIsOwnerOrRootOrSystemLocked();
Preconditions.checkArgument(!hasParentSessionId()); // Don't allow installing child
// sessions
Preconditions.checkArgument(isCommitted() && isSessionReady());
// Since staged sessions are installed during boot, the original reference to status
// receiver from the owner has already been lost. We can safely replace it with a
// status receiver from the system without effecting the flow.
updateRemoteStatusReceiver(statusReceiver);
install();
}
private void updateRemoteStatusReceiver(IntentSender remoteStatusReceiver) {
synchronized (mLock) {
mRemoteStatusReceiver = remoteStatusReceiver;
if (isMultiPackage()) {
final IntentSender childIntentSender = new ChildStatusIntentReceiver(
mChildSessions.clone(), remoteStatusReceiver).getIntentSender();
for (int i = mChildSessions.size() - 1; i >= 0; --i) {
mChildSessions.valueAt(i).mRemoteStatusReceiver = childIntentSender;
}
}
}
}
@Override
@@ -533,7 +616,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public boolean isInTerminalState() {
return isStagedAndInTerminalState();
synchronized (mLock) {
return mSessionApplied || mSessionFailed;
}
}
@Override
@@ -553,54 +638,135 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public boolean isSessionReady() {
return isStagedSessionReady();
synchronized (mLock) {
return mSessionReady;
}
}
@Override
public boolean isSessionApplied() {
return isStagedSessionApplied();
synchronized (mLock) {
return mSessionApplied;
}
}
@Override
public boolean isSessionFailed() {
return isStagedSessionFailed();
synchronized (mLock) {
return mSessionFailed;
}
}
@StagedSessionErrorCode int getSessionErrorCode() {
synchronized (mLock) {
return mSessionErrorCode;
}
}
String getSessionErrorMessage() {
synchronized (mLock) {
return mSessionErrorMessage;
}
}
@Override
public void abandon() {
PackageInstallerSession.this.abandon();
final Runnable r;
synchronized (mLock) {
assertNotChildLocked("StagedSession#abandon");
assertCallerIsOwnerOrRootLocked();
if (isInTerminalState()) {
// We keep the session in the database if it's in a finalized state. It will be
// removed by PackageInstallerService when the last update time is old enough.
// Also, in such cases cleanStageDir() has already been executed so no need to
// do it now.
return;
}
mDestroyed = true;
boolean isCommitted = mCommitted;
List<PackageInstallerSession> childSessions = getChildSessionsLocked();
r = () -> {
assertNotLocked("abandonStaged");
if (isCommitted) {
mStagingManager.abortCommittedSession(this);
}
cleanStageDir(childSessions);
destroyInternal();
dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
maybeCleanUpChildSessions();
};
if (mInPreRebootVerification) {
// Pre-reboot verification is ongoing, not safe to clean up the session yet.
mPendingAbandonCallback = r;
mCallback.onStagedSessionChanged(PackageInstallerSession.this);
return;
}
}
r.run();
}
/**
* Notified by the staging manager that pre-reboot verification is about to start. The
* return value should be checked to decide whether it is OK to start pre-reboot
* verification. In the case of a destroyed session, {@code false} is returned and there is
* no need to start pre-reboot verification.
*/
@Override
public boolean notifyStartPreRebootVerification() {
return notifyStagedStartPreRebootVerification();
synchronized (mLock) {
if (mInPreRebootVerification) {
throw new IllegalStateException("Pre-reboot verification has started");
}
if (mDestroyed) {
return false;
}
mInPreRebootVerification = true;
return true;
}
}
/**
* Notified by the staging manager that pre-reboot verification has ended. Now it is safe to
* clean up the session if {@link #abandon()} has been called previously.
*/
@Override
public void notifyEndPreRebootVerification() {
notifyStagedEndPreRebootVerification();
synchronized (mLock) {
if (!mInPreRebootVerification) {
throw new IllegalStateException("Pre-reboot verification not started");
}
mInPreRebootVerification = false;
}
dispatchPendingAbandonCallback();
}
/**
* Resumes verification process for non-final committed staged session.
*
* Useful if a device gets rebooted before verification is complete and we need to restart
* the verification.
*/
@Override
public void verifySession() {
verifyStagedSession();
assertCallerIsOwnerOrRootOrSystemLocked();
Preconditions.checkArgument(isCommitted());
Preconditions.checkArgument(isStaged());
Preconditions.checkArgument(!mSessionApplied && !mSessionFailed);
verify();
}
private void dispatchPendingAbandonCallback() {
final Runnable callback;
synchronized (mLock) {
callback = mPendingAbandonCallback;
mPendingAbandonCallback = null;
}
if (callback != null) {
callback.run();
}
}
}
/**
* The callback to run when pre-reboot verification has ended. Used by {@link #abandonStaged()}
* to delay session clean-up until it is safe to do so.
*/
@GuardedBy("mLock")
@Nullable
private Runnable mPendingAbandonCallback;
/**
* {@code true} if pre-reboot verification is ongoing which means it is not safe for
* {@link #abandon()} to clean up staging directories.
*/
@GuardedBy("mLock")
private boolean mInPreRebootVerification;
/**
* Path to the validated base APK for this session, which may point at an
* APK inside the session (when the session defines the base), or it may
@@ -838,13 +1004,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mPrepared = prepared;
mCommitted = committed;
mDestroyed = destroyed;
mStagedSessionReady = isReady;
mStagedSessionFailed = isFailed;
mStagedSessionApplied = isApplied;
mStagedSessionErrorCode = stagedSessionErrorCode;
mStagedSessionErrorMessage =
stagedSessionErrorMessage != null ? stagedSessionErrorMessage : "";
mStagedSession = params.isStaged ? new StagedSession() : null;
mStagedSession = params.isStaged ? new StagedSession(isReady, isApplied, isFailed,
stagedSessionErrorCode, stagedSessionErrorMessage) : null;
if (isDataLoaderInstallation()) {
if (isApexSession()) {
@@ -937,10 +1098,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
info.rollbackDataPolicy = params.rollbackDataPolicy;
info.parentSessionId = mParentSessionId;
info.childSessionIds = getChildSessionIdsLocked();
info.isStagedSessionApplied = mStagedSessionApplied;
info.isStagedSessionReady = mStagedSessionReady;
info.isStagedSessionFailed = mStagedSessionFailed;
info.setStagedSessionErrorCode(mStagedSessionErrorCode, mStagedSessionErrorMessage);
info.isStagedSessionApplied = isStagedSessionApplied();
info.isStagedSessionReady = isStagedSessionReady();
info.isStagedSessionFailed = isStagedSessionFailed();
info.setStagedSessionErrorCode(getStagedSessionErrorCode(),
getStagedSessionErrorMessage());
info.createdMillis = createdMillis;
info.updatedMillis = updatedMillis;
}
@@ -975,9 +1137,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
/** Returns true if a staged session has reached a final state and can be forgotten about */
public boolean isStagedAndInTerminalState() {
synchronized (mLock) {
return params.isStaged && (mStagedSessionApplied || mStagedSessionFailed);
}
return params.isStaged && mStagedSession.isInTerminalState();
}
private void assertNotLocked(String cookie) {
@@ -1872,7 +2032,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// Session is sealed and committed but could not be verified, we need to destroy it.
destroyInternal();
if (isStaged()) {
setStagedSessionFailed(
mStagedSession.setSessionFailed(
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, msgWithErrorCode);
// TODO(b/136257624): Remove this once all verification logic has been transferred out
// of StagingManager.
@@ -2011,21 +2171,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
verify();
}
/**
* Resumes verification process for non-final committed staged session.
*
* Useful if a device gets rebooted before verification is complete and we need to restart the
* verification.
*/
void verifyStagedSession() {
assertCallerIsOwnerOrRootOrSystemLocked();
Preconditions.checkArgument(isCommitted());
Preconditions.checkArgument(isStaged());
Preconditions.checkArgument(!mStagedSessionApplied && !mStagedSessionFailed);
verify();
}
private void verify() {
try {
verifyNonStaged();
@@ -2080,36 +2225,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
/**
* Installs apks of staged session while skipping the verification process for a committed and
* ready session.
*/
void installStagedSession(IntentSender statusReceiver) {
assertCallerIsOwnerOrRootOrSystemLocked();
Preconditions.checkArgument(!hasParentSessionId()); // Don't allow installing child sessions
Preconditions.checkArgument(isCommitted() && isStagedSessionReady());
// Since staged sessions are installed during boot, the original reference to status
// receiver from the owner has already been lost. We can safely replace it with a
// status receiver from the system without effecting the flow.
updateRemoteStatusReceiver(statusReceiver);
install();
}
private void updateRemoteStatusReceiver(IntentSender remoteStatusReceiver) {
synchronized (mLock) {
mRemoteStatusReceiver = remoteStatusReceiver;
if (isMultiPackage()) {
final IntentSender childIntentSender =
new ChildStatusIntentReceiver(mChildSessions.clone(), remoteStatusReceiver)
.getIntentSender();
for (int i = mChildSessions.size() - 1; i >= 0; --i) {
mChildSessions.valueAt(i).mRemoteStatusReceiver = childIntentSender;
}
}
}
}
private void install() {
try {
installNonStaged();
@@ -2518,10 +2633,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return false;
}
boolean containsApexSession() {
return sessionContains((s) -> s.isApexSession());
}
boolean containsApkSession() {
return sessionContains((s) -> !s.isApexSession());
}
@@ -3343,6 +3454,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private void abandonNonStaged() {
synchronized (mLock) {
assertNotChildLocked("abandonNonStaged");
assertCallerIsOwnerOrRootLocked();
if (mRelinquished) {
if (LOGD) Slog.d(TAG, "Ignoring abandon after commit relinquished control");
@@ -3354,98 +3466,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
maybeCleanUpChildSessions();
}
private void abandonStaged() {
final Runnable r;
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
if (isStagedAndInTerminalState()) {
// We keep the session in the database if it's in a finalized state. It will be
// removed by PackageInstallerService when the last update time is old enough.
// Also, in such cases cleanStageDir() has already been executed so no need to
// do it now.
return;
}
mDestroyed = true;
boolean isCommitted = mCommitted;
List<PackageInstallerSession> childSessions = getChildSessionsLocked();
r = () -> {
assertNotLocked("abandonStaged");
if (isCommitted) {
mStagingManager.abortCommittedSession(mStagedSession);
}
cleanStageDir(childSessions);
destroyInternal();
dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
maybeCleanUpChildSessions();
};
if (mInPreRebootVerification) {
// Pre-reboot verification is ongoing. It is not safe to clean up the session yet.
mPendingAbandonCallback = r;
mCallback.onStagedSessionChanged(this);
return;
}
@GuardedBy("mLock")
private void assertNotChildLocked(String cookie) {
if (hasParentSessionId()) {
throw new IllegalStateException(cookie + " can't be called on a child session, id="
+ sessionId + " parentId=" + getParentSessionId());
}
r.run();
}
@Override
public void abandon() {
if (hasParentSessionId()) {
throw new IllegalStateException(
"Session " + sessionId + " is a child of multi-package session "
+ getParentSessionId() + " and may not be abandoned directly.");
}
if (params.isStaged) {
abandonStaged();
mStagedSession.abandon();
} else {
abandonNonStaged();
}
}
/**
* Notified by the staging manager that pre-reboot verification is about to start. The return
* value should be checked to decide whether it is OK to start pre-reboot verification. In
* the case of a destroyed session, {@code false} is returned and there is no need to start
* pre-reboot verification.
*/
boolean notifyStagedStartPreRebootVerification() {
synchronized (mLock) {
if (mInPreRebootVerification) {
throw new IllegalStateException("Pre-reboot verification has started");
}
if (mDestroyed) {
return false;
}
mInPreRebootVerification = true;
return true;
}
}
private void dispatchPendingAbandonCallback() {
final Runnable callback;
synchronized (mLock) {
callback = mPendingAbandonCallback;
mPendingAbandonCallback = null;
}
if (callback != null) {
callback.run();
}
}
/**
* Notified by the staging manager that pre-reboot verification has ended. Now it is safe to
* clean up the session if {@link #abandon()} has been called previously.
*/
void notifyStagedEndPreRebootVerification() {
synchronized (mLock) {
if (!mInPreRebootVerification) {
throw new IllegalStateException("Pre-reboot verification not started");
}
mInPreRebootVerification = false;
}
dispatchPendingAbandonCallback();
}
@Override
public boolean isMultiPackage() {
return params.isMultiPackage;
@@ -3930,89 +3967,30 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
/** {@hide} */
void setStagedSessionReady() {
synchronized (mLock) {
// Do not allow destroyed/failed staged session to change state
if (mDestroyed || mStagedSessionFailed) return;
mStagedSessionReady = true;
mStagedSessionApplied = false;
mStagedSessionFailed = false;
mStagedSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR;
mStagedSessionErrorMessage = "";
}
mCallback.onStagedSessionChanged(this);
}
/** {@hide} */
void setStagedSessionFailed(@StagedSessionErrorCode int errorCode, String errorMessage) {
List<PackageInstallerSession> childSessions;
synchronized (mLock) {
// Do not allow destroyed/failed staged session to change state
if (mDestroyed || mStagedSessionFailed) return;
mStagedSessionReady = false;
mStagedSessionApplied = false;
mStagedSessionFailed = true;
mStagedSessionErrorCode = errorCode;
mStagedSessionErrorMessage = errorMessage;
Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage);
childSessions = getChildSessionsLocked();
}
cleanStageDir(childSessions);
mCallback.onStagedSessionChanged(this);
}
/** {@hide} */
void setStagedSessionApplied() {
List<PackageInstallerSession> childSessions;
synchronized (mLock) {
// Do not allow destroyed/failed staged session to change state
if (mDestroyed || mStagedSessionFailed) return;
mStagedSessionReady = false;
mStagedSessionApplied = true;
mStagedSessionFailed = false;
mStagedSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR;
mStagedSessionErrorMessage = "";
Slog.d(TAG, "Marking session " + sessionId + " as applied");
childSessions = getChildSessionsLocked();
}
cleanStageDir(childSessions);
mCallback.onStagedSessionChanged(this);
}
/** {@hide} */
boolean isStagedSessionReady() {
synchronized (mLock) {
return mStagedSessionReady;
}
return params.isStaged && mStagedSession.isSessionReady();
}
/** {@hide} */
boolean isStagedSessionApplied() {
synchronized (mLock) {
return mStagedSessionApplied;
}
return params.isStaged && mStagedSession.isSessionApplied();
}
/** {@hide} */
boolean isStagedSessionFailed() {
synchronized (mLock) {
return mStagedSessionFailed;
}
return params.isStaged && mStagedSession.isSessionFailed();
}
/** {@hide} */
@StagedSessionErrorCode int getStagedSessionErrorCode() {
synchronized (mLock) {
return mStagedSessionErrorCode;
}
return params.isStaged ? mStagedSession.getSessionErrorCode()
: SessionInfo.STAGED_SESSION_NO_ERROR;
}
/** {@hide} */
String getStagedSessionErrorMessage() {
synchronized (mLock) {
return mStagedSessionErrorMessage;
}
return params.isStaged ? mStagedSession.getSessionErrorMessage() : "";
}
private void destroyInternal() {
@@ -4109,11 +4087,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
pw.printPair("params.isStaged", params.isStaged);
pw.printPair("mParentSessionId", mParentSessionId);
pw.printPair("mChildSessionIds", getChildSessionIdsLocked());
pw.printPair("mStagedSessionApplied", mStagedSessionApplied);
pw.printPair("mStagedSessionFailed", mStagedSessionFailed);
pw.printPair("mStagedSessionReady", mStagedSessionReady);
pw.printPair("mStagedSessionErrorCode", mStagedSessionErrorCode);
pw.printPair("mStagedSessionErrorMessage", mStagedSessionErrorMessage);
pw.printPair("mStagedSessionApplied", isStagedSessionApplied());
pw.printPair("mStagedSessionFailed", isStagedSessionFailed());
pw.printPair("mStagedSessionReady", isStagedSessionReady());
pw.printPair("mStagedSessionErrorCode", getStagedSessionErrorCode());
pw.printPair("mStagedSessionErrorMessage", getStagedSessionErrorMessage());
pw.println();
pw.decreaseIndent();
@@ -4279,12 +4257,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage);
writeBooleanAttribute(out, ATTR_STAGED_SESSION, params.isStaged);
writeBooleanAttribute(out, ATTR_IS_READY, mStagedSessionReady);
writeBooleanAttribute(out, ATTR_IS_FAILED, mStagedSessionFailed);
writeBooleanAttribute(out, ATTR_IS_APPLIED, mStagedSessionApplied);
out.attributeInt(null, ATTR_STAGED_SESSION_ERROR_CODE, mStagedSessionErrorCode);
writeBooleanAttribute(out, ATTR_IS_READY, isStagedSessionReady());
writeBooleanAttribute(out, ATTR_IS_FAILED, isStagedSessionFailed());
writeBooleanAttribute(out, ATTR_IS_APPLIED, isStagedSessionApplied());
out.attributeInt(null, ATTR_STAGED_SESSION_ERROR_CODE, getStagedSessionErrorCode());
writeStringAttribute(out, ATTR_STAGED_SESSION_ERROR_MESSAGE,
mStagedSessionErrorMessage);
getStagedSessionErrorMessage());
// TODO(patb,109941548): avoid writing to xml and instead infer / validate this after
// we've read all sessions.
out.attributeInt(null, ATTR_PARENT_SESSION_ID, mParentSessionId);