Extract verication code to a new class (2/n)

* A new class (PackageSessionVerifier) to run pre-reboot verification
  for staged-sessions.
* StagingManager.commitSession() is called after pre-reboot
  verification is done which will then notify the observers
  for staged apex.
* Dead code will be removed in next CLs.

Bug: 161121612
Test: atest StagingManagerTest
Test: atest CtsAtomicInstallTestCases
Test: atest CtsPackageInstallTestCases
Test: atest CtsStagedInstallHostTestCases
Change-Id: I9c80c34543af1d7f363b12af4a3720d3e6d4e7aa
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2595bb4..a5a7c8a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -174,6 +174,7 @@
     private final File mSessionsDir;
 
     private final InternalCallback mInternalCallback = new InternalCallback();
+    private final PackageSessionVerifier mSessionVerifier;
 
     /**
      * Used for generating session IDs. Since this is created at boot time,
@@ -259,6 +260,8 @@
         mApexManager = ApexManager.getInstance();
         mStagingManager = new StagingManager(context, apexParserSupplier,
                 mInstallThread.getLooper());
+        mSessionVerifier = new PackageSessionVerifier(context, mPm, mApexManager,
+                apexParserSupplier, mInstallThread.getLooper());
 
         LocalServices.getService(SystemServiceManager.class).startService(
                 new Lifecycle(context, this));
@@ -1160,6 +1163,11 @@
     }
 
     @Override
+    public PackageSessionVerifier getSessionVerifier() {
+        return mSessionVerifier;
+    }
+
+    @Override
     public void bypassNextStagedInstallerCheck(boolean value) {
         if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
             throw new SecurityException("Caller not allowed to bypass staged installer check");
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 709d359..5c1a5b8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2692,7 +2692,12 @@
         // staged session or not. For a staged session, we will hand it over to the staging
         // manager to complete the installation.
         if (isStaged()) {
-            mStagingManager.commitSession(mStagedSession);
+            mSessionProvider.getSessionVerifier().verifyStaged(mStagedSession, (error, msg) -> {
+                mStagedSession.notifyEndPreRebootVerification();
+                if (error == SessionInfo.STAGED_SESSION_NO_ERROR) {
+                    mStagingManager.commitSession(mStagedSession);
+                }
+            });
             return;
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageSessionProvider.java b/services/core/java/com/android/server/pm/PackageSessionProvider.java
index af11e77..ad5cf13 100644
--- a/services/core/java/com/android/server/pm/PackageSessionProvider.java
+++ b/services/core/java/com/android/server/pm/PackageSessionProvider.java
@@ -16,7 +16,10 @@
 
 package com.android.server.pm;
 
-/** Provides access to individual sessions managed by the install service */
+/**
+ * Provides access to individual sessions managed by the install service as well as utilities
+ * used by the install process.
+ */
 public interface PackageSessionProvider {
 
     /**
@@ -25,4 +28,5 @@
      */
     PackageInstallerSession getSession(int sessionId);
 
+    PackageSessionVerifier getSessionVerifier();
 }
diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
new file mode 100644
index 0000000..3f5410d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.apex.ApexInfo;
+import android.apex.ApexInfoList;
+import android.apex.ApexSessionInfo;
+import android.apex.ApexSessionParams;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.SigningDetails;
+import android.content.pm.parsing.PackageInfoWithoutStateUtils;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.apk.ApkSignatureVerifier;
+
+import com.android.internal.content.PackageHelper;
+import com.android.server.LocalServices;
+import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.rollback.RollbackManagerInternal;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+final class PackageSessionVerifier {
+    private static final String TAG = "PackageSessionVerifier";
+
+    interface Callback {
+        void onResult(int returnCode, String msg);
+    }
+
+    private final Context mContext;
+    private final PackageManagerService mPm;
+    private final ApexManager mApexManager;
+    private final Supplier<PackageParser2> mPackageParserSupplier;
+    // The handler thread to run verification tasks. Since all tasks run in the same thread,
+    // there is no need for synchronization.
+    private final Handler mHandler;
+    // Parent sessions for checking session conflicts.
+    private final List<StagingManager.StagedSession> mStagedSessions = new ArrayList<>();
+
+    PackageSessionVerifier(Context context, PackageManagerService pm,
+            ApexManager apexManager, Supplier<PackageParser2> packageParserSupplier,
+            Looper looper) {
+        mContext = context;
+        mPm = pm;
+        mApexManager = apexManager;
+        mPackageParserSupplier = packageParserSupplier;
+        mHandler = new Handler(looper);
+    }
+
+    /**
+     * Starts pre-reboot verification for the staged-session. This operation is broken into the
+     * following phases:
+     * <ul>
+     *     <li>Checks if multiple active sessions are supported.</li>
+     *     <li>Checks conflicts in rollbacks and overlapping packages.</li>
+     *     <li>Submits apex sessions to apex service.</li>
+     *     <li>Validates signatures of apex files.</li>
+     *     <li>Notifies the result of verification.</li>
+     * </ul>
+     *
+     * Note it is the responsibility of the caller to ensure the staging files remain unchanged
+     * while the verification is in progress.
+     */
+    void verifyStaged(StagingManager.StagedSession session, Callback callback) {
+        Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId());
+        mHandler.post(() -> {
+            try {
+                storeSession(session);
+                checkActiveSessions();
+                checkRollbacks(session);
+                if (session.isMultiPackage()) {
+                    for (StagingManager.StagedSession child : session.getChildSessions()) {
+                        checkOverlaps(session, child);
+                    }
+                } else {
+                    checkOverlaps(session, session);
+                }
+                dispatchVerifyApex(session, callback);
+            } catch (PackageManagerException e) {
+                onVerificationFailure(session, callback, e.error, e.getMessage());
+            }
+        });
+    }
+
+    /**
+     * Stores staged-sessions for checking package overlapping and rollback conflicts.
+     */
+    private void storeSession(StagingManager.StagedSession session) {
+        mStagedSessions.add(session);
+    }
+
+    private void onVerificationSuccess(StagingManager.StagedSession session, Callback callback) {
+        callback.onResult(SessionInfo.STAGED_SESSION_NO_ERROR, null);
+    }
+
+    private void onVerificationFailure(StagingManager.StagedSession session, Callback callback,
+            @SessionInfo.StagedSessionErrorCode int errorCode, String errorMessage) {
+        if (!ensureActiveApexSessionIsAborted(session)) {
+            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.setSessionFailed(errorCode, errorMessage);
+        callback.onResult(errorCode, errorMessage);
+    }
+
+    private void dispatchVerifyApex(StagingManager.StagedSession session, Callback callback) {
+        mHandler.post(() -> {
+            try {
+                verifyApex(session);
+                dispatchEndVerification(session, callback);
+            } catch (PackageManagerException e) {
+                onVerificationFailure(session, callback, e.error, e.getMessage());
+            }
+        });
+    }
+
+    private void dispatchEndVerification(StagingManager.StagedSession session, Callback callback) {
+        mHandler.post(() -> {
+            try {
+                endVerification(session);
+                onVerificationSuccess(session, callback);
+            } catch (PackageManagerException e) {
+                onVerificationFailure(session, callback, e.error, e.getMessage());
+            }
+        });
+    }
+
+    /**
+     * Pre-reboot verification phase for apex files:
+     *
+     * <p><ul>
+     *     <li>submits session to apex service</li>
+     *     <li>validates signatures of apex files</li>
+     * </ul></p>
+     */
+    private void verifyApex(StagingManager.StagedSession session) throws PackageManagerException {
+        int rollbackId = -1;
+        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.
+            final RollbackManagerInternal rm =
+                    LocalServices.getService(RollbackManagerInternal.class);
+            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());
+            } catch (RuntimeException re) {
+                Slog.e(TAG, "Failed to notifyStagedSession for session: "
+                        + session.sessionId(), re);
+            }
+        } else if (isRollback(session)) {
+            rollbackId = retrieveRollbackIdForCommitSession(session.sessionId());
+        }
+
+        final boolean hasApex = session.containsApexSession();
+        // APEX checks. For single-package sessions, check if they contain an APEX. For
+        // multi-package sessions, find all the child sessions that contain an APEX.
+        if (hasApex) {
+            final List<PackageInfo> apexPackages = submitSessionToApexService(session, rollbackId);
+            for (int i = 0, size = apexPackages.size(); i < size; i++) {
+                validateApexSignature(apexPackages.get(i));
+            }
+            final PackageManagerInternal packageManagerInternal =
+                    LocalServices.getService(PackageManagerInternal.class);
+            packageManagerInternal.pruneCachedApksInApex(apexPackages);
+        }
+    }
+
+    /**
+     * Pre-reboot verification phase for wrapping up:
+     * <p><ul>
+     *     <li>enables checkpoint if supported</li>
+     *     <li>marks session as ready</li>
+     * </ul></p>
+     */
+    private void endVerification(StagingManager.StagedSession session)
+            throws PackageManagerException {
+        // Before marking the session as ready, start checkpoint service if available
+        try {
+            if (PackageHelper.getStorageManager().supportsCheckpoint()) {
+                PackageHelper.getStorageManager().startCheckpoint(2);
+            }
+        } catch (Exception e) {
+            // Failed to get hold of StorageManager
+            Slog.e(TAG, "Failed to get hold of StorageManager", e);
+            throw new PackageManagerException(
+                    SessionInfo.STAGED_SESSION_UNKNOWN,
+                    "Failed to get hold of StorageManager");
+        }
+        // Proactively mark session as ready before calling apexd. Although this call order
+        // looks counter-intuitive, this is the easiest way to ensure that session won't end up
+        // in the inconsistent state:
+        //  - If device gets rebooted right before call to apexd, then apexd will never activate
+        //      apex files of this staged session. This will result in StagingManager failing
+        //      the session.
+        // 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.setSessionReady();
+        if (session.isSessionReady()) {
+            final boolean hasApex = session.containsApexSession();
+            if (hasApex) {
+                mApexManager.markStagedSessionReady(session.sessionId());
+            }
+        }
+    }
+
+    /**
+     * Validates the signature used to sign the container of the new apex package
+     *
+     * @param newApexPkg The new apex package that is being installed
+     */
+    private void validateApexSignature(PackageInfo newApexPkg) throws PackageManagerException {
+        // Get signing details of the new package
+        final String apexPath = newApexPkg.applicationInfo.sourceDir;
+        final String packageName = newApexPkg.packageName;
+        int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
+                newApexPkg.applicationInfo.targetSdkVersion);
+
+        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+        final ParseResult<SigningDetails> newResult = ApkSignatureVerifier.verify(
+                input.reset(), apexPath, minSignatureScheme);
+        if (newResult.isError()) {
+            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                    "Failed to parse APEX package " + apexPath + " : "
+                            + newResult.getException(), newResult.getException());
+        }
+        final SigningDetails newSigningDetails = newResult.getResult();
+
+        // Get signing details of the existing package
+        final PackageInfo existingApexPkg = mApexManager.getPackageInfo(packageName,
+                ApexManager.MATCH_ACTIVE_PACKAGE);
+        if (existingApexPkg == null) {
+            // This should never happen, because submitSessionToApexService ensures that no new
+            // apexes were installed.
+            throw new IllegalStateException("Unknown apex package " + packageName);
+        }
+
+        final ParseResult<SigningDetails> existingResult = ApkSignatureVerifier.verify(
+                input.reset(), existingApexPkg.applicationInfo.sourceDir,
+                SigningDetails.SignatureSchemeVersion.JAR);
+        if (existingResult.isError()) {
+            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                    "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir
+                            + " : " + existingResult.getException(), existingResult.getException());
+        }
+        final SigningDetails existingSigningDetails = existingResult.getResult();
+
+        // Verify signing details for upgrade
+        if (newSigningDetails.checkCapability(existingSigningDetails,
+                SigningDetails.CertCapabilities.INSTALLED_DATA)
+                || existingSigningDetails.checkCapability(newSigningDetails,
+                SigningDetails.CertCapabilities.ROLLBACK)) {
+            return;
+        }
+
+        throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                "APK-container signature of APEX package " + packageName + " with version "
+                        + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not"
+                        + " compatible with the one currently installed on device");
+    }
+
+    private List<PackageInfo> submitSessionToApexService(StagingManager.StagedSession session,
+            int rollbackId) throws PackageManagerException {
+        final IntArray childSessionIds = new IntArray();
+        if (session.isMultiPackage()) {
+            for (StagingManager.StagedSession s : session.getChildSessions()) {
+                if (s.isApexSession()) {
+                    childSessionIds.add(s.sessionId());
+                }
+            }
+        }
+        ApexSessionParams apexSessionParams = new ApexSessionParams();
+        apexSessionParams.sessionId = session.sessionId();
+        apexSessionParams.childSessionIds = childSessionIds.toArray();
+        if (session.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK) {
+            apexSessionParams.isRollback = true;
+            apexSessionParams.rollbackId = rollbackId;
+        } else {
+            if (rollbackId != -1) {
+                apexSessionParams.hasRollbackEnabled = true;
+                apexSessionParams.rollbackId = rollbackId;
+            }
+        }
+        // submitStagedSession will throw a PackageManagerException if apexd verification fails,
+        // which will be propagated to populate stagedSessionErrorMessage of this session.
+        final ApexInfoList apexInfoList = mApexManager.submitStagedSession(apexSessionParams);
+        final List<PackageInfo> result = new ArrayList<>();
+        final List<String> apexPackageNames = new ArrayList<>();
+        for (ApexInfo apexInfo : apexInfoList.apexInfos) {
+            final PackageInfo packageInfo;
+            final int flags = PackageManager.GET_META_DATA;
+            try (PackageParser2 packageParser = mPackageParserSupplier.get()) {
+                File apexFile = new File(apexInfo.modulePath);
+                final ParsedPackage parsedPackage = packageParser.parsePackage(
+                        apexFile, flags, false);
+                packageInfo = PackageInfoWithoutStateUtils.generate(parsedPackage, apexInfo, flags);
+                if (packageInfo == null) {
+                    throw new PackageManagerException(
+                            SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                            "Unable to generate package info: " + apexInfo.modulePath);
+                }
+            } catch (PackageManagerException e) {
+                throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                        "Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e);
+            }
+            result.add(packageInfo);
+            apexPackageNames.add(packageInfo.packageName);
+        }
+        Slog.d(TAG, "Session " + session.sessionId() + " has following APEX packages: "
+                + apexPackageNames);
+        return result;
+    }
+
+    private int retrieveRollbackIdForCommitSession(int sessionId) throws PackageManagerException {
+        RollbackManager rm = mContext.getSystemService(RollbackManager.class);
+
+        final List<RollbackInfo> rollbacks = rm.getRecentlyCommittedRollbacks();
+        for (int i = 0, size = rollbacks.size(); i < size; i++) {
+            final RollbackInfo rollback = rollbacks.get(i);
+            if (rollback.getCommittedSessionId() == sessionId) {
+                return rollback.getRollbackId();
+            }
+        }
+        throw new PackageManagerException(
+                SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                "Could not find rollback id for commit session: " + sessionId);
+    }
+
+    private static boolean isRollback(StagingManager.StagedSession session) {
+        return session.sessionParams().installReason == PackageManager.INSTALL_REASON_ROLLBACK;
+    }
+
+    private static boolean isApexSessionFinalized(ApexSessionInfo info) {
+        /* checking if the session is in a final state, i.e., not active anymore */
+        return info.isUnknown || info.isActivationFailed || info.isSuccess
+                || info.isReverted;
+    }
+
+    private boolean ensureActiveApexSessionIsAborted(StagingManager.StagedSession session) {
+        if (!session.containsApexSession()) {
+            return true;
+        }
+        final int sessionId = session.sessionId();
+        final ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(sessionId);
+        if (apexSession == null || isApexSessionFinalized(apexSession)) {
+            return true;
+        }
+        return mApexManager.abortStagedSession(sessionId);
+    }
+
+    /**
+     * Checks if multiple staged-sessions are supported. It is supported only when the system
+     * supports checkpoint.
+     */
+    private void checkActiveSessions() throws PackageManagerException {
+        final boolean supportsCheckpoint;
+        try {
+            supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
+        } catch (RemoteException e) {
+            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                    "Can't query fs-checkpoint status : " + e);
+        }
+        int activeSessions = 0;
+        for (StagingManager.StagedSession stagedSession : mStagedSessions) {
+            if (stagedSession.isDestroyed() || stagedSession.isInTerminalState()) {
+                continue;
+            }
+            ++activeSessions;
+        }
+        if (!supportsCheckpoint && activeSessions > 1) {
+            throw new PackageManagerException(
+                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                    "Cannot stage multiple sessions without checkpoint support");
+        }
+    }
+
+    /**
+     * Fails non-rollback sessions if any rollback session exists. A rollback session might cause
+     * downgrade of SDK extension which in turn will result in dependency violation of other
+     * non-rollback sessions.
+     */
+    private void checkRollbacks(StagingManager.StagedSession session)
+            throws PackageManagerException {
+        for (StagingManager.StagedSession stagedSession : mStagedSessions) {
+            if (stagedSession.isDestroyed() || stagedSession.isInTerminalState()) {
+                continue;
+            }
+            if (isRollback(session) && !isRollback(stagedSession)) {
+                // If the new session is a rollback, then it gets priority. The existing
+                // session is failed to reduce risk and avoid an SDK extension dependency
+                // violation.
+                if (!ensureActiveApexSessionIsAborted(stagedSession)) {
+                    Slog.e(TAG, "Failed to abort apex session " + stagedSession.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.
+                }
+                stagedSession.setSessionFailed(
+                        SessionInfo.STAGED_SESSION_CONFLICT,
+                        "Session was failed by rollback session: " + session.sessionId());
+                Slog.i(TAG, "Session " + stagedSession.sessionId() + " is marked failed due to "
+                        + "rollback session: " + session.sessionId());
+            } else if (!isRollback(session) && isRollback(stagedSession)) {
+                throw new PackageManagerException(
+                        SessionInfo.STAGED_SESSION_CONFLICT,
+                        "Session was failed by rollback session: " + stagedSession.sessionId());
+
+            }
+        }
+    }
+
+    /**
+     * Fails the session that is committed later when overlapping packages are detected.
+     *
+     * @param parent The parent session.
+     * @param child The child session whose package name will be checked.
+     *              This will equal to {@code parent} for a single-package session.
+     */
+    private void checkOverlaps(StagingManager.StagedSession parent,
+            StagingManager.StagedSession child) throws PackageManagerException {
+        final String packageName = child.getPackageName();
+        if (packageName == null) {
+            throw new PackageManagerException(
+                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                    "Cannot stage session " + child.sessionId() + " with package name null");
+        }
+        for (StagingManager.StagedSession stagedSession : mStagedSessions) {
+            if (stagedSession.isDestroyed() || stagedSession.isInTerminalState()
+                    || stagedSession.sessionId() == parent.sessionId()) {
+                continue;
+            }
+            if (stagedSession.sessionContains(s -> packageName.equals(s.getPackageName()))) {
+                if (stagedSession.getCommittedMillis() < parent.getCommittedMillis()) {
+                    // Fail the session committed later when there are overlapping packages
+                    throw new PackageManagerException(
+                            SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                            "Package: " + packageName + " in session: "
+                                    + child.sessionId()
+                                    + " has been staged already by session: "
+                                    + stagedSession.sessionId());
+                } else {
+                    stagedSession.setSessionFailed(
+                            SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                            "Package: " + packageName + " in session: "
+                                    + stagedSession.sessionId()
+                                    + " has been staged already by session: "
+                                    + child.sessionId());
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index a2f48c7..c10071b 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -107,7 +107,6 @@
     private final PowerManager mPowerManager;
     private final Context mContext;
     @VisibleForTesting
-    final PreRebootVerificationHandler mPreRebootVerificationHandler;
     private final Supplier<PackageParser2> mPackageParserSupplier;
 
     private final File mFailureReasonFile = new File("/metadata/staged-install/failure_reason.txt");
@@ -173,7 +172,6 @@
 
         mApexManager = apexManager;
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        mPreRebootVerificationHandler = new PreRebootVerificationHandler(looper);
 
         if (mFailureReasonFile.exists()) {
             try (BufferedReader reader = new BufferedReader(new FileReader(mFailureReasonFile))) {
@@ -663,10 +661,16 @@
         }
     }
 
+    @VisibleForTesting
     void commitSession(@NonNull StagedSession session) {
-        // Store this parent session which will be used to check overlapping later
         createSession(session);
-        mPreRebootVerificationHandler.startPreRebootVerification(session);
+        handleCommittedSession(session);
+    }
+
+    private void handleCommittedSession(@NonNull StagedSession session) {
+        if (session.isSessionReady() && session.containsApexSession()) {
+            notifyStagedApexObservers();
+        }
     }
 
     private int getSessionIdForParentOrSelf(StagedSession session) {
@@ -1234,237 +1238,4 @@
             }
         }
     }
-
-    @VisibleForTesting
-    final class PreRebootVerificationHandler extends Handler {
-
-        PreRebootVerificationHandler(Looper looper) {
-            super(looper);
-        }
-
-        /**
-         * Handler for states of pre reboot verification. The states are arranged linearly (shown
-         * below) with each state either calling the next state, or calling some other method that
-         * eventually calls the next state.
-         *
-         * <p><ul>
-         *     <li>MSG_PRE_REBOOT_VERIFICATION_START</li>
-         *     <li>MSG_PRE_REBOOT_VERIFICATION_APEX</li>
-         *     <li>MSG_PRE_REBOOT_VERIFICATION_END</li>
-         * </ul></p>
-         *
-         * Details about each of state can be found in corresponding handler of node.
-         */
-        private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1;
-        private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2;
-        @VisibleForTesting
-        static final int MSG_PRE_REBOOT_VERIFICATION_END = 3;
-
-        @Override
-        public void handleMessage(Message msg) {
-            final int sessionId = msg.arg1;
-            final int rollbackId = msg.arg2;
-            final StagedSession session = (StagedSession) msg.obj;
-            if (session.isDestroyed() || session.isSessionFailed()) {
-                // No point in running verification on a destroyed/failed session
-                onPreRebootVerificationComplete(session);
-                return;
-            }
-            try {
-                switch (msg.what) {
-                    case MSG_PRE_REBOOT_VERIFICATION_START:
-                        handlePreRebootVerification_Start(session);
-                        break;
-                    case MSG_PRE_REBOOT_VERIFICATION_APEX:
-                        handlePreRebootVerification_Apex(session, rollbackId);
-                        break;
-                    case MSG_PRE_REBOOT_VERIFICATION_END:
-                        handlePreRebootVerification_End(session);
-                        break;
-                }
-            } catch (Exception e) {
-                Slog.e(TAG, "Pre-reboot verification failed due to unhandled exception", e);
-                onPreRebootVerificationFailure(session,
-                        SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
-                        "Pre-reboot verification failed due to unhandled exception: " + e);
-            }
-        }
-
-        // Method for starting the pre-reboot verification
-        private synchronized void startPreRebootVerification(
-                @NonNull StagedSession session) {
-            mBootCompleted.thenRun(() -> {
-                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(StagedSession session,
-                @SessionInfo.StagedSessionErrorCode int errorCode, String errorMessage) {
-            if (!ensureActiveApexSessionIsAborted(session)) {
-                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.setSessionFailed(errorCode, errorMessage);
-            onPreRebootVerificationComplete(session);
-        }
-
-        // Things to do when pre-reboot verification completes for a particular sessionId
-        private void onPreRebootVerificationComplete(StagedSession session) {
-            int sessionId = session.sessionId();
-            Slog.d(TAG, "Stopping preRebootVerification for session " + sessionId);
-            session.notifyEndPreRebootVerification();
-        }
-
-        private void notifyPreRebootVerification_Start_Complete(
-                @NonNull StagedSession session, int rollbackId) {
-            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, session.sessionId(), rollbackId,
-                    session).sendToTarget();
-        }
-
-        private void notifyPreRebootVerification_Apex_Complete(
-                @NonNull StagedSession session) {
-            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, session.sessionId(), -1, session)
-                    .sendToTarget();
-        }
-
-        /**
-         * A placeholder state for starting the pre reboot verification.
-         *
-         * See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification
-         */
-        private void handlePreRebootVerification_Start(@NonNull StagedSession session) {
-            try {
-                if (session.isMultiPackage()) {
-                    for (StagedSession s : session.getChildSessions()) {
-                        checkNonOverlappingWithStagedSessions(s);
-                    }
-                } else {
-                    checkNonOverlappingWithStagedSessions(session);
-                }
-            } catch (PackageManagerException e) {
-                onPreRebootVerificationFailure(session, e.error, e.getMessage());
-                return;
-            }
-
-            int rollbackId = -1;
-            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.
-                final RollbackManagerInternal rm =
-                        LocalServices.getService(RollbackManagerInternal.class);
-                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());
-                } catch (RuntimeException re) {
-                    Slog.e(TAG, "Failed to notifyStagedSession for session: "
-                            + session.sessionId(), re);
-                }
-            } else if (session.sessionParams().installReason
-                    == PackageManager.INSTALL_REASON_ROLLBACK) {
-                try {
-                    rollbackId = retrieveRollbackIdForCommitSession(session.sessionId());
-                } catch (PackageManagerException e) {
-                    onPreRebootVerificationFailure(session, e.error, e.getMessage());
-                    return;
-                }
-            }
-
-            notifyPreRebootVerification_Start_Complete(session, rollbackId);
-        }
-
-        /**
-         * Pre-reboot verification state for apex files:
-         *
-         * <p><ul>
-         *     <li>submits session to apex service</li>
-         *     <li>validates signatures of apex files</li>
-         * </ul></p>
-         */
-        private void handlePreRebootVerification_Apex(
-                @NonNull StagedSession session, int rollbackId) {
-            final boolean hasApex = session.containsApexSession();
-
-            // APEX checks. For single-package sessions, check if they contain an APEX. For
-            // multi-package sessions, find all the child sessions that contain an APEX.
-            if (hasApex) {
-                final List<PackageInfo> apexPackages;
-                try {
-                    apexPackages = submitSessionToApexService(session, rollbackId);
-                    for (int i = 0, size = apexPackages.size(); i < size; i++) {
-                        validateApexSignature(apexPackages.get(i));
-                    }
-                } catch (PackageManagerException e) {
-                    onPreRebootVerificationFailure(session, e.error, e.getMessage());
-                    return;
-                }
-
-                final PackageManagerInternal packageManagerInternal =
-                        LocalServices.getService(PackageManagerInternal.class);
-                packageManagerInternal.pruneCachedApksInApex(apexPackages);
-            }
-
-            notifyPreRebootVerification_Apex_Complete(session);
-        }
-
-        /**
-         * Pre-reboot verification state for wrapping up:
-         * <p><ul>
-         *     <li>enables rollback if required</li>
-         *     <li>marks session as ready</li>
-         * </ul></p>
-         */
-        private void handlePreRebootVerification_End(@NonNull StagedSession session) {
-            // Before marking the session as ready, start checkpoint service if available
-            try {
-                if (PackageHelper.getStorageManager().supportsCheckpoint()) {
-                    PackageHelper.getStorageManager().startCheckpoint(2);
-                }
-            } catch (Exception e) {
-                // Failed to get hold of StorageManager
-                Slog.e(TAG, "Failed to get hold of StorageManager", e);
-                onPreRebootVerificationFailure(session, SessionInfo.STAGED_SESSION_UNKNOWN,
-                        "Failed to get hold of StorageManager");
-                return;
-            }
-
-            // Stop pre-reboot verification before marking session ready. From this point on, if we
-            // abandon the session then it will be cleaned up immediately. If session is abandoned
-            // after this point, then even if for some reason system tries to install the session
-            // or activate its apex, there won't be any files to work with as they will be cleaned
-            // up by the system as part of abandonment. If session is abandoned before this point,
-            // then the session is already destroyed and cannot be marked ready anymore.
-            onPreRebootVerificationComplete(session);
-
-            // Proactively mark session as ready before calling apexd. Although this call order
-            // looks counter-intuitive, this is the easiest way to ensure that session won't end up
-            // in the inconsistent state:
-            //  - If device gets rebooted right before call to apexd, then apexd will never activate
-            //      apex files of this staged session. This will result in StagingManager failing
-            //      the session.
-            // 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.setSessionReady();
-            if (session.isSessionReady()) {
-                final boolean hasApex = session.containsApexSession();
-                if (hasApex) {
-                    try {
-                        mApexManager.markStagedSessionReady(session.sessionId());
-                        notifyStagedApexObservers();
-                    } catch (PackageManagerException e) {
-                        session.setSessionFailed(e.error, e.getMessage());
-                        return;
-                    }
-                }
-            }
-        }
-    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index 6a4d69f..776bf8b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -726,10 +726,9 @@
         {
             FakeStagedSession session = new FakeStagedSession(239);
             session.setIsApex(true);
-            mStagingManager.createSession(session);
-
+            session.setSessionReady();
             mockApexManagerGetStagedApexInfoWithSessionId();
-            triggerEndOfPreRebootVerification(session);
+            mStagingManager.commitSession(session);
 
             assertThat(session.isSessionReady()).isTrue();
             ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass(
@@ -744,9 +743,8 @@
             Mockito.clearInvocations(observer);
             FakeStagedSession session = new FakeStagedSession(240);
             session.setIsApex(true);
-            mStagingManager.createSession(session);
-
-            triggerEndOfPreRebootVerification(session);
+            session.setSessionReady();
+            mStagingManager.commitSession(session);
 
             assertThat(session.isSessionReady()).isTrue();
             ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass(
@@ -762,9 +760,8 @@
             Mockito.clearInvocations(observer);
             FakeStagedSession session = new FakeStagedSession(241);
             session.setIsApex(true);
-            mStagingManager.createSession(session);
-
-            triggerEndOfPreRebootVerification(session);
+            session.setSessionReady();
+            mStagingManager.commitSession(session);
 
             assertThat(session.isSessionReady()).isTrue();
             verify(observer, never()).onApexStaged(any());
@@ -800,21 +797,13 @@
 
         //  Trigger end of pre-reboot verification
         FakeStagedSession session = new FakeStagedSession(239);
-        mStagingManager.createSession(session);
+        session.setSessionReady();
+        mStagingManager.commitSession(session);
 
-        triggerEndOfPreRebootVerification(session);
         assertThat(session.isSessionReady()).isTrue();
         verify(observer, never()).onApexStaged(any());
     }
 
-    private void triggerEndOfPreRebootVerification(StagingManager.StagedSession session) {
-        StagingManager.PreRebootVerificationHandler handler =
-                mStagingManager.mPreRebootVerificationHandler;
-        Message msg =  handler.obtainMessage(
-                handler.MSG_PRE_REBOOT_VERIFICATION_END, session.sessionId(), -1, session);
-        handler.handleMessage(msg);
-    }
-
     private StagingManager.StagedSession createSession(int sessionId, String packageName,
             long committedMillis) {
         PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(