From 24b4ed567ed6d53b407bc8a92e3cd394f58e8d8c Mon Sep 17 00:00:00 2001 From: Gavin Corkery Date: Wed, 29 Apr 2020 16:30:10 +0100 Subject: [PATCH] Mark apex sessions as successful upon boot completion Instead of marking apex sessions as successful immediately, wait until the boot has completed. This ensures that the checkpoint has been committed. This is necessary since the apex session state lives in /metadata, so any changes to the session will persist regardless of the checkpoint state. This change ensures that APK and APEX sessions behave the same way if the device reboots after activation, but before boot completion. Create a new lifecycle class within Staging Manager to receive calls for boot completion. Test: atest ApexRollbackTests Test: Manual test of rebooting device after activation and before boot completion, and ensuring session is not marked as successful Bug: 154705130 Change-Id: I822e46123d3f4d3d84e0c50c9aa791c1a81e8f50 --- .../com/android/server/pm/StagingManager.java | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index b805a2456b468..79805e3b42ae8 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -69,6 +69,8 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageHelper; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; +import com.android.server.SystemService; +import com.android.server.SystemServiceManager; import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; @@ -110,6 +112,9 @@ public class StagingManager { private final List mFailedPackageNames = new ArrayList<>(); private String mNativeFailureReason; + @GuardedBy("mSuccessfulStagedSessionIds") + private final List mSuccessfulStagedSessionIds = new ArrayList<>(); + StagingManager(PackageInstallerService pi, Context context, Supplier packageParserSupplier) { mPi = pi; @@ -122,6 +127,34 @@ public class StagingManager { BackgroundThread.get().getLooper()); } + /** + This class manages lifecycle events for StagingManager. + */ + public static final class Lifecycle extends SystemService { + private static StagingManager sStagingManager; + + public Lifecycle(Context context) { + super(context); + } + + void startService(StagingManager stagingManager) { + sStagingManager = stagingManager; + LocalServices.getService(SystemServiceManager.class).startService(this); + } + + @Override + public void onStart() { + // no-op + } + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_BOOT_COMPLETED && sStagingManager != null) { + sStagingManager.markStagedSessionsAsSuccessful(); + } + } + } + private void updateStoredSession(@NonNull PackageInstallerSession sessionInfo) { synchronized (mStagedSessions) { PackageInstallerSession storedSession = mStagedSessions.get(sessionInfo.sessionId); @@ -652,7 +685,22 @@ public class StagingManager { Slog.d(TAG, "Marking session " + session.sessionId + " as applied"); session.setStagedSessionApplied(); if (hasApex) { - mApexManager.markStagedSessionSuccessful(session.sessionId); + try { + if (supportsCheckpoint()) { + // Store the session ID, which will be marked as successful by ApexManager + // upon boot completion. + synchronized (mSuccessfulStagedSessionIds) { + mSuccessfulStagedSessionIds.add(session.sessionId); + } + } else { + // Mark sessions as successful immediately on non-checkpointing devices. + mApexManager.markStagedSessionSuccessful(session.sessionId); + } + } catch (RemoteException e) { + Slog.w(TAG, "Checkpoint support unknown, marking session as successful " + + "immediately."); + mApexManager.markStagedSessionSuccessful(session.sessionId); + } } } @@ -1121,7 +1169,16 @@ public class StagingManager { } } + void markStagedSessionsAsSuccessful() { + synchronized (mSuccessfulStagedSessionIds) { + for (int i = 0; i < mSuccessfulStagedSessionIds.size(); i++) { + mApexManager.markStagedSessionSuccessful(mSuccessfulStagedSessionIds.get(i)); + } + } + } + void systemReady() { + new Lifecycle(mContext).startService(this); // Register the receiver of boot completed intent for staging manager. mContext.registerReceiver(new BroadcastReceiver() { @Override