From 7cbf063fa9a73b47aba7a28a8589c68aa3edd1d8 Mon Sep 17 00:00:00 2001 From: JW Wang Date: Fri, 11 Dec 2020 11:27:50 +0800 Subject: [PATCH 1/3] Extract staged session code to a separate class (1/n) Now StagingManager manages a collection of StagedSessions instead of PackageInstallerSessions which might expose info not directly related to staged sessions and not useful to StagingManager. In next CL, we will move fields/methods that are related to staged sessions into PackageInstallerSession#StagedSession to improve the cohesion. Bug: 166694095 Test: atest StagedInstallTest Test: atest StagingManagerTest Test: atest PackageInstallerSessionTest Change-Id: I4eeeea280d10fc8f49a3aa053fd907be5c2e3706 --- .../server/pm/PackageInstallerService.java | 14 +- .../server/pm/PackageInstallerSession.java | 156 ++++++++- .../com/android/server/pm/StagingManager.java | 297 ++++++++++-------- .../android/server/pm/StagingManagerTest.java | 19 +- 4 files changed, 338 insertions(+), 148 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 9e48ddd63c01a..2d393c089411e 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -291,23 +291,23 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } void restoreAndApplyStagedSessionIfNeeded() { - List stagedSessionsToRestore = new ArrayList<>(); + List stagedSessionsToRestore = new ArrayList<>(); synchronized (mSessions) { for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); if (session.isStaged()) { - stagedSessionsToRestore.add(session); + stagedSessionsToRestore.add(session.mStagedSession); } } } // Don't hold mSessions lock when calling restoreSession, since it might trigger an APK // atomic install which needs to query sessions, which requires lock on mSessions. boolean isDeviceUpgrading = mPm.isDeviceUpgrading(); - for (PackageInstallerSession session : stagedSessionsToRestore) { - if (!session.isStagedAndInTerminalState() && session.hasParentSessionId() + for (StagingManager.StagedSession session : stagedSessionsToRestore) { + if (!session.isInTerminalState() && session.hasParentSessionId() && getSession(session.getParentSessionId()) == null) { - session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, - "An orphan staged session " + session.sessionId + " is found, " + session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + "An orphan staged session " + session.sessionId() + " is found, " + "parent " + session.getParentSessionId() + " is missing"); } mStagingManager.restoreSession(session, isDeviceUpgrading); @@ -1399,7 +1399,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements @Override public void run() { if (session.isStaged() && !success) { - mStagingManager.abortSession(session); + mStagingManager.abortSession(session.mStagedSession); } synchronized (mSessions) { if (!session.isStaged() || !success) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index b8daa832cd2d7..8a35257597d84 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -440,6 +440,153 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private String mStagedSessionErrorMessage; + @Nullable + final StagedSession mStagedSession; + + @VisibleForTesting + public class StagedSession implements StagingManager.StagedSession { + @Override + public List getChildSessions() { + if (!params.isMultiPackage) { + return Collections.EMPTY_LIST; + } + synchronized (mLock) { + int size = mChildSessions.size(); + List childSessions = new ArrayList<>(size); + for (int i = 0; i < size; ++i) { + childSessions.add(mChildSessions.valueAt(i).mStagedSession); + } + return childSessions; + } + } + + @Override + public SessionParams sessionParams() { + return params; + } + + @Override + public boolean isMultiPackage() { + return params.isMultiPackage; + } + + @Override + public boolean isApexSession() { + return (params.installFlags & PackageManager.INSTALL_APEX) != 0; + } + + @Override + public int sessionId() { + return sessionId; + } + + @Override + public boolean containsApexSession() { + return PackageInstallerSession.this.containsApexSession(); + } + + @Override + public String getPackageName() { + return PackageInstallerSession.this.getPackageName(); + } + + @Override + public void setSessionReady() { + setStagedSessionReady(); + } + + @Override + public void setSessionFailed(int errorCode, String errorMessage) { + setStagedSessionFailed(errorCode, errorMessage); + } + + @Override + public void setSessionApplied() { + setStagedSessionApplied(); + } + + @Override + public boolean containsApkSession() { + return PackageInstallerSession.this.containsApkSession(); + } + + @Override + public void installSession(IntentSender statusReceiver) { + installStagedSession(statusReceiver); + + } + + @Override + public boolean hasParentSessionId() { + return PackageInstallerSession.this.hasParentSessionId(); + } + + @Override + public int getParentSessionId() { + return PackageInstallerSession.this.getParentSessionId(); + } + + @Override + public boolean isCommitted() { + return PackageInstallerSession.this.isCommitted(); + } + + @Override + public boolean isInTerminalState() { + return isStagedAndInTerminalState(); + } + + @Override + public boolean isDestroyed() { + return PackageInstallerSession.this.isDestroyed(); + } + + @Override + public long getCommittedMillis() { + return PackageInstallerSession.this.getCommittedMillis(); + } + + @Override + public boolean sessionContains(Predicate filter) { + return PackageInstallerSession.this.sessionContains(s -> filter.test(s.mStagedSession)); + } + + @Override + public boolean isSessionReady() { + return isStagedSessionReady(); + } + + @Override + public boolean isSessionApplied() { + return isStagedSessionApplied(); + } + + @Override + public boolean isSessionFailed() { + return isStagedSessionFailed(); + } + + @Override + public void abandon() { + PackageInstallerSession.this.abandon(); + } + + @Override + public boolean notifyStartPreRebootVerification() { + return notifyStagedStartPreRebootVerification(); + } + + @Override + public void notifyEndPreRebootVerification() { + notifyStagedEndPreRebootVerification(); + } + + @Override + public void verifySession() { + verifyStagedSession(); + } + } + /** * 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. @@ -697,6 +844,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionErrorCode = stagedSessionErrorCode; mStagedSessionErrorMessage = stagedSessionErrorMessage != null ? stagedSessionErrorMessage : ""; + mStagedSession = params.isStaged ? new StagedSession() : null; if (isDataLoaderInstallation()) { if (isApexSession()) { @@ -1728,7 +1876,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, msgWithErrorCode); // TODO(b/136257624): Remove this once all verification logic has been transferred out // of StagingManager. - mStagingManager.notifyVerificationComplete(this); + mStagingManager.notifyVerificationComplete(mStagedSession); } else { // Dispatch message to remove session from PackageInstallerService. dispatchSessionFinished(error, msg, null); @@ -1847,7 +1995,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { .write(); } if (params.isStaged) { - mStagingManager.commitSession(this); + mStagingManager.commitSession(mStagedSession); // TODO(b/136257624): CTS test fails if we don't send session finished broadcast, even // though ideally, we just need to send session committed broadcast. dispatchSessionFinished(INSTALL_SUCCEEDED, "Session staged", null); @@ -2188,7 +2336,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (isStaged()) { // TODO(b/136257624): Remove this once all verification logic has been transferred out // of StagingManager. - mStagingManager.notifyPreRebootVerification_Apk_Complete(this); + mStagingManager.notifyPreRebootVerification_Apk_Complete(mStagedSession); // TODO(b/136257624): We also need to destroy internals for verified staged session, // otherwise file descriptors are never closed for verified staged session until reboot return; @@ -3223,7 +3371,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { r = () -> { assertNotLocked("abandonStaged"); if (isCommitted) { - mStagingManager.abortCommittedSession(this); + mStagingManager.abortCommittedSession(mStagedSession); } cleanStageDir(childSessions); destroyInternal(); diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 222874d3eb823..545567c26972e 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -32,6 +32,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; +import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser.PackageParserException; @@ -82,6 +83,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; import java.util.function.Supplier; /** @@ -102,7 +104,7 @@ public class StagingManager { private String mFailureReason; @GuardedBy("mStagedSessions") - private final SparseArray mStagedSessions = new SparseArray<>(); + private final SparseArray mStagedSessions = new SparseArray<>(); @GuardedBy("mFailedPackageNames") private final List mFailedPackageNames = new ArrayList<>(); @@ -111,6 +113,35 @@ public class StagingManager { @GuardedBy("mSuccessfulStagedSessionIds") private final List mSuccessfulStagedSessionIds = new ArrayList<>(); + interface StagedSession { + boolean isMultiPackage(); + boolean isApexSession(); + boolean isCommitted(); + boolean isInTerminalState(); + boolean isDestroyed(); + boolean isSessionReady(); + boolean isSessionApplied(); + boolean isSessionFailed(); + List getChildSessions(); + String getPackageName(); + int getParentSessionId(); + int sessionId(); + PackageInstaller.SessionParams sessionParams(); + boolean sessionContains(Predicate filter); + boolean containsApkSession(); + boolean containsApexSession(); + void setSessionReady(); + void setSessionFailed(@StagedSessionErrorCode int errorCode, String errorMessage); + void setSessionApplied(); + void installSession(IntentSender statusReceiver); + boolean hasParentSessionId(); + long getCommittedMillis(); + void abandon(); + boolean notifyStartPreRebootVerification(); + void notifyEndPreRebootVerification(); + void verifySession(); + } + StagingManager(Context context, Supplier packageParserSupplier) { mContext = context; mPackageParserSupplier = packageParserSupplier; @@ -213,20 +244,20 @@ public class StagingManager { + " compatible with the one currently installed on device"); } - private List submitSessionToApexService(@NonNull PackageInstallerSession session, + private List submitSessionToApexService(@NonNull StagedSession session, int rollbackId) throws PackageManagerException { final IntArray childSessionIds = new IntArray(); if (session.isMultiPackage()) { - for (PackageInstallerSession s : session.getChildSessions()) { + for (StagedSession s : session.getChildSessions()) { if (s.isApexSession()) { - childSessionIds.add(s.sessionId); + childSessionIds.add(s.sessionId()); } } } ApexSessionParams apexSessionParams = new ApexSessionParams(); - apexSessionParams.sessionId = session.sessionId; + apexSessionParams.sessionId = session.sessionId(); apexSessionParams.childSessionIds = childSessionIds.toArray(); - if (session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK) { + if (session.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK) { apexSessionParams.isRollback = true; apexSessionParams.rollbackId = rollbackId; } else { @@ -269,7 +300,7 @@ public class StagingManager { result.add(packageInfo); apexPackageNames.add(packageInfo.packageName); } - Slog.d(TAG, "Session " + session.sessionId + " has following APEX packages: " + Slog.d(TAG, "Session " + session.sessionId() + " has following APEX packages: " + apexPackageNames); return result; } @@ -288,22 +319,23 @@ public class StagingManager { "Could not find rollback id for commit session: " + sessionId); } - private void checkRequiredVersionCode(final PackageInstallerSession session, + private void checkRequiredVersionCode(final StagedSession session, final PackageInfo activePackage) throws PackageManagerException { - if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) { + if (session.sessionParams().requiredInstalledVersionCode + == PackageManager.VERSION_CODE_HIGHEST) { return; } final long activeVersion = activePackage.applicationInfo.longVersionCode; - if (activeVersion != session.params.requiredInstalledVersionCode) { + if (activeVersion != session.sessionParams().requiredInstalledVersionCode) { throw new PackageManagerException( SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Installed version of APEX package " + activePackage.packageName + " does not match required. Active version: " + activeVersion - + " required: " + session.params.requiredInstalledVersionCode); + + " required: " + session.sessionParams().requiredInstalledVersionCode); } } - private void checkDowngrade(final PackageInstallerSession session, + private void checkDowngrade(final StagedSession session, final PackageInfo activePackage, final PackageInfo newPackage) throws PackageManagerException { final long activeVersion = activePackage.applicationInfo.longVersionCode; @@ -311,7 +343,7 @@ public class StagingManager { final boolean isAppDebuggable = (activePackage.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; final boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted( - session.params.installFlags, isAppDebuggable); + session.sessionParams().installFlags, isAppDebuggable); if (activeVersion > newVersionCode && !allowsDowngrade) { throw new PackageManagerException( SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, @@ -363,10 +395,10 @@ public class StagingManager { /** * Utility function for extracting apex sessions out of multi-package/single session. */ - private List extractApexSessions(PackageInstallerSession session) { - List apexSessions = new ArrayList<>(); + private List extractApexSessions(StagedSession session) { + List apexSessions = new ArrayList<>(); if (session.isMultiPackage()) { - for (PackageInstallerSession s : session.getChildSessions()) { + for (StagedSession s : session.getChildSessions()) { if (s.containsApexSession()) { apexSessions.add(s); } @@ -383,14 +415,14 @@ public class StagingManager { * * @throws PackageManagerException if any apk-in-apex failed to install */ - private void checkInstallationOfApkInApexSuccessful(PackageInstallerSession session) + private void checkInstallationOfApkInApexSuccessful(StagedSession session) throws PackageManagerException { - final List apexSessions = extractApexSessions(session); + final List apexSessions = extractApexSessions(session); if (apexSessions.isEmpty()) { return; } - for (PackageInstallerSession apexSession : apexSessions) { + for (StagedSession apexSession : apexSessions) { String packageName = apexSession.getPackageName(); if (!mApexManager.isApkInApexInstallSuccess(packageName)) { throw new PackageManagerException(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, @@ -405,16 +437,16 @@ public class StagingManager { * directory directly by PackageManager, as such, RollbackManager need to handle their data * separately here. */ - private void snapshotAndRestoreForApexSession(PackageInstallerSession session) { + private void snapshotAndRestoreForApexSession(StagedSession session) { boolean doSnapshotOrRestore = - (session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0 - || session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK; + (session.sessionParams().installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0 + || session.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK; if (!doSnapshotOrRestore) { return; } // Find all the apex sessions that needs processing - final List apexSessions = extractApexSessions(session); + final List apexSessions = extractApexSessions(session); if (apexSessions.isEmpty()) { return; } @@ -475,7 +507,7 @@ public class StagingManager { * session package names. * Logging needs to wait until the ACTION_BOOT_COMPLETED broadcast is sent. */ - private void prepareForLoggingApexdRevert(@NonNull PackageInstallerSession session, + private void prepareForLoggingApexdRevert(@NonNull StagedSession session, @NonNull String nativeFailureReason) { synchronized (mFailedPackageNames) { mNativeFailureReason = nativeFailureReason; @@ -485,15 +517,15 @@ public class StagingManager { } } - private void resumeSession(@NonNull PackageInstallerSession session) + private void resumeSession(@NonNull StagedSession session) throws PackageManagerException { - Slog.d(TAG, "Resuming session " + session.sessionId); + Slog.d(TAG, "Resuming session " + session.sessionId()); final boolean hasApex = session.containsApexSession(); ApexSessionInfo apexSessionInfo = null; if (hasApex) { // Check with apexservice whether the apex packages have been activated. - apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId); + apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId()); // Prepare for logging a native crash during boot, if one occurred. if (apexSessionInfo != null && !TextUtils.isEmpty( @@ -506,7 +538,7 @@ public class StagingManager { // pre-reboot verification, perhaps because the device rebooted in the meantime. // Greedily re-trigger the pre-reboot verification. We want to avoid marking it as // failed when not in checkpoint mode, hence it is being processed separately. - Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to " + Slog.d(TAG, "Found pending staged session " + session.sessionId() + " still to " + "be verified, resuming pre-reboot verification"); mPreRebootVerificationHandler.startPreRebootVerification(session); return; @@ -523,21 +555,21 @@ public class StagingManager { // mode. If not, we fail all sessions. if (supportsCheckpoint() && !needsCheckpoint()) { String revertMsg = "Reverting back to safe state. Marking " - + session.sessionId + " as failed."; + + session.sessionId() + " as failed."; final String reasonForRevert = getReasonForRevert(); if (!TextUtils.isEmpty(reasonForRevert)) { revertMsg += " Reason for revert: " + reasonForRevert; } Slog.d(TAG, revertMsg); - session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg); + session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg); return; } } catch (RemoteException e) { // Cannot continue staged install without knowing if fs-checkpoint is supported Slog.e(TAG, "Checkpoint support unknown. Aborting staged install for session " - + session.sessionId, e); + + session.sessionId(), e); // TODO: Mark all staged sessions together and reboot only once - session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, + session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, "Checkpoint support unknown. Aborting staged install."); if (hasApex) { mApexManager.revertActiveSessions(); @@ -568,7 +600,7 @@ public class StagingManager { // Apexd did not apply the session for some unknown reason. There is no // guarantee that apexd will install it next time. Safer to proactively mark // it as failed. - final String errorMsg = "Staged session " + session.sessionId + "at boot " + final String errorMsg = "Staged session " + session.sessionId() + "at boot " + "didn't activate nor fail. Marking it as failed anyway."; throw new PackageManagerException( SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg); @@ -580,38 +612,38 @@ public class StagingManager { checkInstallationOfApkInApexSuccessful(session); checkDuplicateApkInApex(session); snapshotAndRestoreForApexSession(session); - Slog.i(TAG, "APEX packages in session " + session.sessionId + Slog.i(TAG, "APEX packages in session " + session.sessionId() + " were successfully activated. Proceeding with APK packages, if any"); } // The APEX part of the session is activated, proceed with the installation of APKs. - Slog.d(TAG, "Installing APK packages in session " + session.sessionId); + Slog.d(TAG, "Installing APK packages in session " + session.sessionId()); installApksInSession(session); - Slog.d(TAG, "Marking session " + session.sessionId + " as applied"); - session.setStagedSessionApplied(); + Slog.d(TAG, "Marking session " + session.sessionId() + " as applied"); + session.setSessionApplied(); if (hasApex) { try { if (supportsCheckpoint()) { // Store the session ID, which will be marked as successful by ApexManager // upon boot completion. synchronized (mSuccessfulStagedSessionIds) { - mSuccessfulStagedSessionIds.add(session.sessionId); + mSuccessfulStagedSessionIds.add(session.sessionId()); } } else { // Mark sessions as successful immediately on non-checkpointing devices. - mApexManager.markStagedSessionSuccessful(session.sessionId); + mApexManager.markStagedSessionSuccessful(session.sessionId()); } } catch (RemoteException e) { Slog.w(TAG, "Checkpoint support unknown, marking session as successful " + "immediately."); - mApexManager.markStagedSessionSuccessful(session.sessionId); + mApexManager.markStagedSessionSuccessful(session.sessionId()); } } } - void onInstallationFailure(PackageInstallerSession session, PackageManagerException e) { - session.setStagedSessionFailed(e.error, e.getMessage()); - abortCheckpoint(session.sessionId, e.getMessage()); + void onInstallationFailure(StagedSession session, PackageManagerException e) { + session.setSessionFailed(e.error, e.getMessage()); + abortCheckpoint(session.sessionId(), e.getMessage()); // If checkpoint is not supported, we have to handle failure for one staged session. if (!session.containsApexSession()) { @@ -641,26 +673,26 @@ public class StagingManager { /** * Throws a PackageManagerException if there are duplicate packages in apk and apk-in-apex. */ - private void checkDuplicateApkInApex(@NonNull PackageInstallerSession session) + private void checkDuplicateApkInApex(@NonNull StagedSession session) throws PackageManagerException { if (!session.isMultiPackage()) { return; } final Set apkNames = new ArraySet<>(); - for (PackageInstallerSession s : session.getChildSessions()) { + for (StagedSession s : session.getChildSessions()) { if (!s.isApexSession()) { apkNames.add(s.getPackageName()); } } - final List apexSessions = extractApexSessions(session); - for (PackageInstallerSession apexSession : apexSessions) { + final List apexSessions = extractApexSessions(session); + for (StagedSession apexSession : apexSessions) { String packageName = apexSession.getPackageName(); for (String apkInApex : mApexManager.getApksInApex(packageName)) { if (!apkNames.add(apkInApex)) { throw new PackageManagerException( SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "Package: " + packageName + " in session: " - + apexSession.sessionId + " has duplicate apk-in-apex: " + + apexSession.sessionId() + " has duplicate apk-in-apex: " + apkInApex, null); } @@ -668,14 +700,14 @@ public class StagingManager { } } - private void installApksInSession(PackageInstallerSession session) + private void installApksInSession(StagedSession session) throws PackageManagerException { if (!session.containsApkSession()) { return; } final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync(); - session.installStagedSession(receiver.getIntentSender()); + session.installSession(receiver.getIntentSender()); final Intent result = receiver.getResult(); final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); @@ -683,31 +715,31 @@ public class StagingManager { final String errorMessage = result.getStringExtra( PackageInstaller.EXTRA_STATUS_MESSAGE); Slog.e(TAG, "Failure to install APK staged session " - + session.sessionId + " [" + errorMessage + "]"); + + session.sessionId() + " [" + errorMessage + "]"); throw new PackageManagerException( SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage); } } - void commitSession(@NonNull PackageInstallerSession session) { + void commitSession(@NonNull StagedSession session) { // Store this parent session which will be used to check overlapping later createSession(session); mPreRebootVerificationHandler.startPreRebootVerification(session); } - private int getSessionIdForParentOrSelf(PackageInstallerSession session) { - return session.hasParentSessionId() ? session.getParentSessionId() : session.sessionId; + private int getSessionIdForParentOrSelf(StagedSession session) { + return session.hasParentSessionId() ? session.getParentSessionId() : session.sessionId(); } - private PackageInstallerSession getParentSessionOrSelf(PackageInstallerSession session) { + private StagedSession getParentSessionOrSelf(StagedSession session) { return session.hasParentSessionId() ? getStagedSession(session.getParentSessionId()) : session; } - private boolean isRollback(PackageInstallerSession session) { - final PackageInstallerSession root = getParentSessionOrSelf(session); - return root.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK; + private boolean isRollback(StagedSession session) { + final StagedSession root = getParentSessionOrSelf(session); + return root.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK; } /** @@ -722,7 +754,7 @@ public class StagingManager { * @throws PackageManagerException if session fails the check */ @VisibleForTesting - void checkNonOverlappingWithStagedSessions(@NonNull PackageInstallerSession session) + void checkNonOverlappingWithStagedSessions(@NonNull StagedSession session) throws PackageManagerException { if (session.isMultiPackage()) { // We cannot say a parent session overlaps until we process its children @@ -732,7 +764,7 @@ public class StagingManager { String packageName = session.getPackageName(); if (packageName == null) { throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, - "Cannot stage session " + session.sessionId + " with package name null"); + "Cannot stage session " + session.sessionId() + " with package name null"); } boolean supportsCheckpoint = ((StorageManager) mContext.getSystemService( @@ -742,9 +774,9 @@ public class StagingManager { synchronized (mStagedSessions) { for (int i = 0; i < mStagedSessions.size(); i++) { - final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i); + final StagedSession stagedSession = mStagedSessions.valueAt(i); if (stagedSession.hasParentSessionId() || !stagedSession.isCommitted() - || stagedSession.isStagedAndInTerminalState() + || stagedSession.isInTerminalState() || stagedSession.isDestroyed()) { continue; } @@ -759,8 +791,8 @@ public class StagingManager { // From here on, stagedSession is a parent active staged session // Check if session is one of the active sessions - if (getSessionIdForParentOrSelf(session) == stagedSession.sessionId) { - Slog.w(TAG, "Session " + session.sessionId + " is already staged"); + if (getSessionIdForParentOrSelf(session) == stagedSession.sessionId()) { + Slog.w(TAG, "Session " + session.sessionId() + " is already staged"); continue; } @@ -769,24 +801,25 @@ public class StagingManager { if (isRollback) { // If the new session is a rollback, then it gets priority. The existing // session is failed to unblock rollback. - final PackageInstallerSession root = stagedSession; + final StagedSession root = stagedSession; if (!ensureActiveApexSessionIsAborted(root)) { - Slog.e(TAG, "Failed to abort apex session " + root.sessionId); + Slog.e(TAG, "Failed to abort apex session " + root.sessionId()); // Safe to ignore active apex session abort failure since session // will be marked failed on next step and staging directory for session // will be deleted. } - root.setStagedSessionFailed( + root.setSessionFailed( SessionInfo.STAGED_SESSION_CONFLICT, - "Session was blocking rollback session: " + session.sessionId); - Slog.i(TAG, "Session " + root.sessionId + " is marked failed due to " - + "blocking rollback session: " + session.sessionId); + "Session was blocking rollback session: " + session.sessionId()); + Slog.i(TAG, "Session " + root.sessionId() + " is marked failed due to " + + "blocking rollback session: " + session.sessionId()); } else { throw new PackageManagerException( SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Package: " + session.getPackageName() + " in session: " - + session.sessionId + " has been staged already by session:" - + " " + stagedSession.sessionId, null); + + session.sessionId() + + " has been staged already by session: " + + stagedSession.sessionId(), null); } } @@ -803,24 +836,24 @@ public class StagingManager { } @VisibleForTesting - void createSession(@NonNull PackageInstallerSession sessionInfo) { + void createSession(@NonNull StagedSession sessionInfo) { synchronized (mStagedSessions) { - mStagedSessions.append(sessionInfo.sessionId, sessionInfo); + mStagedSessions.append(sessionInfo.sessionId(), sessionInfo); } } - void abortSession(@NonNull PackageInstallerSession session) { + void abortSession(@NonNull StagedSession session) { synchronized (mStagedSessions) { - mStagedSessions.remove(session.sessionId); + mStagedSessions.remove(session.sessionId()); } } /** *

Abort committed staged session */ - void abortCommittedSession(@NonNull PackageInstallerSession session) { - int sessionId = session.sessionId; - if (session.isStagedAndInTerminalState()) { + void abortCommittedSession(@NonNull StagedSession session) { + int sessionId = session.sessionId(); + if (session.isInTerminalState()) { Slog.w(TAG, "Cannot abort session in final state: " + sessionId); return; } @@ -834,12 +867,12 @@ public class StagingManager { } // A session could be marked ready once its pre-reboot verification ends - if (session.isStagedSessionReady()) { + if (session.isSessionReady()) { if (!ensureActiveApexSessionIsAborted(session)) { // Failed to ensure apex session is aborted, so it can still be staged. We can still // safely cleanup the staged session since pre-reboot verification is complete. // Also, cleaning up the stageDir prevents the apex from being activated. - Slog.e(TAG, "Failed to abort apex session " + session.sessionId); + Slog.e(TAG, "Failed to abort apex session " + session.sessionId()); } } @@ -853,15 +886,15 @@ public class StagingManager { * * @return returns true if it is ensured that there is no active apex session, otherwise false */ - private boolean ensureActiveApexSessionIsAborted(PackageInstallerSession session) { + private boolean ensureActiveApexSessionIsAborted(StagedSession session) { if (!session.containsApexSession()) { return true; } - final ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId); + final ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId()); if (apexSession == null || isApexSessionFinalized(apexSession)) { return true; } - return mApexManager.abortStagedSession(session.sessionId); + return mApexManager.abortStagedSession(session.sessionId()); } private boolean isApexSessionFinalized(ApexSessionInfo session) { @@ -878,7 +911,7 @@ public class StagingManager { || apexSessionInfo.isRevertFailed; } - void restoreSession(@NonNull PackageInstallerSession session, boolean isDeviceUpgrading) { + void restoreSession(@NonNull StagedSession session, boolean isDeviceUpgrading) { if (session.hasParentSessionId()) { // Only parent sessions can be restored return; @@ -887,16 +920,16 @@ public class StagingManager { createSession(session); // The preconditions used during pre-reboot verification might have changed when device // is upgrading. Updated staged sessions to activation failed before we resume the session. - PackageInstallerSession sessionToResume = session; - if (isDeviceUpgrading && !sessionToResume.isStagedAndInTerminalState()) { - sessionToResume.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + StagedSession sessionToResume = session; + if (isDeviceUpgrading && !sessionToResume.isInTerminalState()) { + sessionToResume.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "Build fingerprint has changed"); return; } checkStateAndResume(sessionToResume); } - private void checkStateAndResume(@NonNull PackageInstallerSession session) { + private void checkStateAndResume(@NonNull StagedSession session) { // Do not resume session if boot completed already if (SystemProperties.getBoolean("sys.boot_completed", false)) { return; @@ -907,7 +940,7 @@ public class StagingManager { return; } // Check the state of the session and decide what to do next. - if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) { + if (session.isSessionFailed() || session.isSessionApplied()) { // Final states, nothing to do. return; } @@ -916,7 +949,7 @@ public class StagingManager { session.abandon(); return; } - if (!session.isStagedSessionReady()) { + if (!session.isSessionReady()) { // The framework got restarted before the pre-reboot verification could complete, // restart the verification. mPreRebootVerificationHandler.startPreRebootVerification(session); @@ -998,8 +1031,8 @@ public class StagingManager { } } - private PackageInstallerSession getStagedSession(int sessionId) { - PackageInstallerSession session; + private StagedSession getStagedSession(int sessionId) { + StagedSession session; synchronized (mStagedSessions) { session = mStagedSessions.get(sessionId); } @@ -1009,20 +1042,20 @@ public class StagingManager { // TODO(b/136257624): Temporary API to let PMS communicate with StagingManager. When all // verification logic is extracted out of StagingManager into PMS, we can remove // this. - void notifyVerificationComplete(PackageInstallerSession session) { + void notifyVerificationComplete(StagedSession session) { mPreRebootVerificationHandler.onPreRebootVerificationComplete(session); } // TODO(b/136257624): Temporary API to let PMS communicate with StagingManager. When all // verification logic is extracted out of StagingManager into PMS, we can remove // this. - void notifyPreRebootVerification_Apk_Complete(@NonNull PackageInstallerSession session) { + void notifyPreRebootVerification_Apk_Complete(@NonNull StagedSession session) { mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(session); } private final class PreRebootVerificationHandler extends Handler { // Hold sessions before handler gets ready to do the verification. - private List mPendingSessions; + private List mPendingSessions; private boolean mIsReady; PreRebootVerificationHandler(Looper looper) { @@ -1052,8 +1085,8 @@ public class StagingManager { public void handleMessage(Message msg) { final int sessionId = msg.arg1; final int rollbackId = msg.arg2; - final PackageInstallerSession session = (PackageInstallerSession) msg.obj; - if (session.isDestroyed() || session.isStagedSessionFailed()) { + final StagedSession session = (StagedSession) msg.obj; + if (session.isDestroyed() || session.isSessionFailed()) { // No point in running verification on a destroyed/failed session onPreRebootVerificationComplete(session); return; @@ -1086,7 +1119,7 @@ public class StagingManager { mIsReady = true; if (mPendingSessions != null) { for (int i = 0; i < mPendingSessions.size(); i++) { - PackageInstallerSession session = mPendingSessions.get(i); + StagedSession session = mPendingSessions.get(i); startPreRebootVerification(session); } mPendingSessions = null; @@ -1095,7 +1128,7 @@ public class StagingManager { // Method for starting the pre-reboot verification private synchronized void startPreRebootVerification( - @NonNull PackageInstallerSession session) { + @NonNull StagedSession session) { if (!mIsReady) { if (mPendingSessions == null) { mPendingSessions = new ArrayList<>(); @@ -1104,47 +1137,47 @@ public class StagingManager { return; } - if (session.notifyStagedStartPreRebootVerification()) { - int sessionId = session.sessionId; + if (session.notifyStartPreRebootVerification()) { + int sessionId = session.sessionId(); Slog.d(TAG, "Starting preRebootVerification for session " + sessionId); obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, -1, session) .sendToTarget(); } } - private void onPreRebootVerificationFailure(PackageInstallerSession session, + private void onPreRebootVerificationFailure(StagedSession session, @SessionInfo.StagedSessionErrorCode int errorCode, String errorMessage) { if (!ensureActiveApexSessionIsAborted(session)) { - Slog.e(TAG, "Failed to abort apex session " + session.sessionId); + Slog.e(TAG, "Failed to abort apex session " + session.sessionId()); // Safe to ignore active apex session abortion failure since session will be marked // failed on next step and staging directory for session will be deleted. } - session.setStagedSessionFailed(errorCode, errorMessage); + session.setSessionFailed(errorCode, errorMessage); onPreRebootVerificationComplete(session); } // Things to do when pre-reboot verification completes for a particular sessionId - private void onPreRebootVerificationComplete(PackageInstallerSession session) { - int sessionId = session.sessionId; + private void onPreRebootVerificationComplete(StagedSession session) { + int sessionId = session.sessionId(); Slog.d(TAG, "Stopping preRebootVerification for session " + sessionId); - session.notifyStagedEndPreRebootVerification(); + session.notifyEndPreRebootVerification(); } private void notifyPreRebootVerification_Start_Complete( - @NonNull PackageInstallerSession session, int rollbackId) { - obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, session.sessionId, rollbackId, session) - .sendToTarget(); + @NonNull StagedSession session, int rollbackId) { + obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, session.sessionId(), rollbackId, + session).sendToTarget(); } private void notifyPreRebootVerification_Apex_Complete( - @NonNull PackageInstallerSession session) { - obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, session.sessionId, -1, session) + @NonNull StagedSession session) { + obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, session.sessionId(), -1, session) .sendToTarget(); } private void notifyPreRebootVerification_Apk_Complete( - @NonNull PackageInstallerSession session) { - obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, session.sessionId, -1, session) + @NonNull StagedSession session) { + obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, session.sessionId(), -1, session) .sendToTarget(); } @@ -1153,10 +1186,10 @@ public class StagingManager { * * See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification */ - private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) { + private void handlePreRebootVerification_Start(@NonNull StagedSession session) { try { if (session.isMultiPackage()) { - for (PackageInstallerSession s : session.getChildSessions()) { + for (StagedSession s : session.getChildSessions()) { checkNonOverlappingWithStagedSessions(s); } } else { @@ -1168,7 +1201,8 @@ public class StagingManager { } int rollbackId = -1; - if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) { + if ((session.sessionParams().installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) + != 0) { // If rollback is enabled for this session, we call through to the RollbackManager // with the list of sessions it must enable rollback for. Note that // notifyStagedSession is a synchronous operation. @@ -1177,14 +1211,15 @@ public class StagingManager { try { // NOTE: To stay consistent with the non-staged install flow, we don't fail the // entire install if rollbacks can't be enabled. - rollbackId = rm.notifyStagedSession(session.sessionId); + rollbackId = rm.notifyStagedSession(session.sessionId()); } catch (RuntimeException re) { Slog.e(TAG, "Failed to notifyStagedSession for session: " - + session.sessionId, re); + + session.sessionId(), re); } - } else if (session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK) { + } else if (session.sessionParams().installReason + == PackageManager.INSTALL_REASON_ROLLBACK) { try { - rollbackId = retrieveRollbackIdForCommitSession(session.sessionId); + rollbackId = retrieveRollbackIdForCommitSession(session.sessionId()); } catch (PackageManagerException e) { onPreRebootVerificationFailure(session, e.error, e.getMessage()); return; @@ -1203,7 +1238,7 @@ public class StagingManager { *

*/ private void handlePreRebootVerification_Apex( - @NonNull PackageInstallerSession session, int rollbackId) { + @NonNull StagedSession session, int rollbackId) { final boolean hasApex = session.containsApexSession(); // APEX checks. For single-package sessions, check if they contain an APEX. For @@ -1233,12 +1268,12 @@ public class StagingManager { * {@link PackageManagerService} for verification and it notifies back the result via * {@link #notifyPreRebootVerification_Apk_Complete} */ - private void handlePreRebootVerification_Apk(@NonNull PackageInstallerSession session) { + private void handlePreRebootVerification_Apk(@NonNull StagedSession session) { if (!session.containsApkSession()) { notifyPreRebootVerification_Apk_Complete(session); return; } - session.verifyStagedSession(); + session.verifySession(); } /** @@ -1248,7 +1283,7 @@ public class StagingManager { *
  • marks session as ready
  • *

    */ - private void handlePreRebootVerification_End(@NonNull PackageInstallerSession session) { + private void handlePreRebootVerification_End(@NonNull StagedSession session) { // Before marking the session as ready, start checkpoint service if available try { IStorageManager storageManager = PackageHelper.getStorageManager(); @@ -1280,15 +1315,15 @@ public class StagingManager { // On the other hand, if the order of the calls was inverted (first call apexd, then // mark session as ready), then if a device gets rebooted right after the call to apexd, // only apex part of the train will be applied, leaving device in an inconsistent state. - Slog.d(TAG, "Marking session " + session.sessionId + " as ready"); - session.setStagedSessionReady(); - if (session.isStagedSessionReady()) { + Slog.d(TAG, "Marking session " + session.sessionId() + " as ready"); + session.setSessionReady(); + if (session.isSessionReady()) { final boolean hasApex = session.containsApexSession(); if (hasApex) { try { - mApexManager.markStagedSessionReady(session.sessionId); + mApexManager.markStagedSessionReady(session.sessionId()); } catch (PackageManagerException e) { - session.setStagedSessionFailed(e.error, e.getMessage()); + session.setSessionFailed(e.error, e.getMessage()); return; } } diff --git a/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java index 4870c9e070cdf..79935c23774fe 100644 --- a/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java @@ -33,8 +33,11 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.io.File; +import java.util.function.Predicate; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -70,8 +73,8 @@ public class StagingManagerTest { public void checkNonOverlappingWithStagedSessions_laterSessionShouldNotFailEarlierOnes() throws Exception { // Create 2 sessions with overlapping packages - PackageInstallerSession session1 = createSession(111, "com.foo", 1); - PackageInstallerSession session2 = createSession(222, "com.foo", 2); + StagingManager.StagedSession session1 = createSession(111, "com.foo", 1); + StagingManager.StagedSession session2 = createSession(222, "com.foo", 2); mStagingManager.createSession(session1); mStagingManager.createSession(session2); @@ -82,7 +85,7 @@ public class StagingManagerTest { () -> mStagingManager.checkNonOverlappingWithStagedSessions(session2)); } - private PackageInstallerSession createSession(int sessionId, String packageName, + private StagingManager.StagedSession createSession(int sessionId, String packageName, long committedMillis) { PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); @@ -121,8 +124,12 @@ public class StagingManagerTest { /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.STAGED_SESSION_NO_ERROR, /* stagedSessionErrorMessage */ "no error"); - session = spy(session); - doReturn(packageName).when(session).getPackageName(); - return session; + StagingManager.StagedSession stagedSession = spy(session.mStagedSession); + doReturn(packageName).when(stagedSession).getPackageName(); + doAnswer(invocation -> { + Predicate filter = invocation.getArgument(0); + return filter.test(stagedSession); + }).when(stagedSession).sessionContains(any()); + return stagedSession; } } From 2cf104161fcd842334b9ed57aa36f76e3019fbf1 Mon Sep 17 00:00:00 2001 From: JW Wang Date: Fri, 11 Dec 2020 14:58:21 +0800 Subject: [PATCH 2/3] 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 --- .../server/pm/PackageInstallerSession.java | 494 +++++++++--------- 1 file changed, 236 insertions(+), 258 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 8a35257597d84..41eb3bc2d6441 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -429,22 +429,45 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private ArrayMap> 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 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 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 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 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 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 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 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); From 16c3ec7781d790343090859cb0c493c56193f5dc Mon Sep 17 00:00:00 2001 From: JW Wang Date: Tue, 15 Dec 2020 15:34:48 +0800 Subject: [PATCH 3/3] Some cleanup (3/n) Bug: 166694095 Test: atest StagedInstallTest Change-Id: Ibbf7f77ca512498c7b4b3080a11174ac96bb932c --- .../java/com/android/server/pm/PackageInstallerSession.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 41eb3bc2d6441..4fe72c26b7778 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -575,8 +575,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void installSession(IntentSender statusReceiver) { assertCallerIsOwnerOrRootOrSystemLocked(); - Preconditions.checkArgument(!hasParentSessionId()); // Don't allow installing child - // sessions + assertNotChildLocked("StagedSession#installSession"); Preconditions.checkArgument(isCommitted() && isSessionReady()); // Since staged sessions are installed during boot, the original reference to status @@ -750,7 +749,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { public void verifySession() { assertCallerIsOwnerOrRootOrSystemLocked(); Preconditions.checkArgument(isCommitted()); - Preconditions.checkArgument(isStaged()); Preconditions.checkArgument(!mSessionApplied && !mSessionFailed); verify(); }