Prevent exceptions during staged install from crashing system server

An unhandled exception during staged install at boot time  will cause
system server to crash and restart. Upon restart, staged install will
resume again and it will cause system server to crash again. Thus
creating a loop.

Instead of allowing the exception to crash the system server, we now
catch it and fail the corresponding staged session with appropriate
message.

Bug: 170784748
Test: manual
Test: verified that without this fix, any unhandled exception sends
system server into a crash loop

Change-Id: I9abec5d2401af95ecb095fa3c45960d2f15d4e74
Merged-In: I9abec5d2401af95ecb095fa3c45960d2f15d4e74
(cherry picked from commit 1c3e99b6c5a13ec2d7a8d3e618ce459fec0de7b7)
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 88e8676..f779b6e 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -564,7 +564,8 @@
         }
     }
 
-    private void resumeSession(@NonNull PackageInstallerSession session) {
+    private void resumeSession(@NonNull PackageInstallerSession session)
+            throws PackageManagerException {
         Slog.d(TAG, "Resuming session " + session.sessionId);
 
         final boolean hasApex = sessionContainsApex(session);
@@ -628,10 +629,8 @@
             if (apexSessionInfo == null) {
                 final String errorMsg = "apexd did not know anything about a staged session "
                         + "supposed to be activated";
-                session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
-                        errorMsg);
-                abortCheckpoint(session.sessionId, errorMsg);
-                return;
+                throw new PackageManagerException(
+                        SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
             }
             if (isApexSessionFailed(apexSessionInfo)) {
                 String errorMsg = "APEX activation failed. Check logcat messages from apexd "
@@ -640,10 +639,8 @@
                     errorMsg = "Session reverted due to crashing native process: "
                             + mNativeFailureReason;
                 }
-                session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
-                        errorMsg);
-                abortCheckpoint(session.sessionId, errorMsg);
-                return;
+                throw new PackageManagerException(
+                        SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
             }
             if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
                 // Apexd did not apply the session for some unknown reason. There is no
@@ -651,42 +648,20 @@
                 // it as failed.
                 final String errorMsg = "Staged session " + session.sessionId + "at boot "
                         + "didn't activate nor fail. Marking it as failed anyway.";
-                session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
-                        errorMsg);
-                abortCheckpoint(session.sessionId, errorMsg);
-                return;
+                throw new PackageManagerException(
+                        SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
             }
         }
         // Handle apk and apk-in-apex installation
-        try {
-            if (hasApex) {
-                checkInstallationOfApkInApexSuccessful(session);
-                snapshotAndRestoreForApexSession(session);
-                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);
-            installApksInSession(session);
-        } catch (PackageManagerException e) {
-            session.setStagedSessionFailed(e.error, e.getMessage());
-            abortCheckpoint(session.sessionId, e.getMessage());
-
-            // If checkpoint is not supported, we have to handle failure for one staged session.
-            if (!hasApex) {
-                return;
-            }
-
-            if (!mApexManager.revertActiveSessions()) {
-                Slog.e(TAG, "Failed to abort APEXd session");
-            } else {
-                Slog.e(TAG,
-                        "Successfully aborted apexd session. Rebooting device in order to revert "
-                                + "to the previous state of APEXd.");
-                mPowerManager.reboot(null);
-            }
-            return;
+        if (hasApex) {
+            checkInstallationOfApkInApexSuccessful(session);
+            snapshotAndRestoreForApexSession(session);
+            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);
+        installApksInSession(session);
 
         Slog.d(TAG, "Marking session " + session.sessionId + " as applied");
         session.setStagedSessionApplied();
@@ -722,6 +697,25 @@
         return ret;
     }
 
+    void onInstallationFailure(PackageInstallerSession session, PackageManagerException e) {
+        session.setStagedSessionFailed(e.error, e.getMessage());
+        abortCheckpoint(session.sessionId, e.getMessage());
+
+        // If checkpoint is not supported, we have to handle failure for one staged session.
+        if (!sessionContainsApex(session)) {
+            return;
+        }
+
+        if (!mApexManager.revertActiveSessions()) {
+            Slog.e(TAG, "Failed to abort APEXd session");
+        } else {
+            Slog.e(TAG,
+                    "Successfully aborted apexd session. Rebooting device in order to revert "
+                            + "to the previous state of APEXd.");
+            mPowerManager.reboot(null);
+        }
+    }
+
     @NonNull
     private PackageInstallerSession createAndWriteApkSession(
             @NonNull PackageInstallerSession originalSession, boolean preReboot)
@@ -1185,7 +1179,16 @@
         } else {
             // Session had already being marked ready. Start the checks to verify if there is any
             // follow-up work.
-            resumeSession(session);
+            try {
+                resumeSession(session);
+            } catch (PackageManagerException e) {
+                onInstallationFailure(session, e);
+            } catch (Exception e) {
+                Slog.e(TAG, "Staged install failed due to unhandled exception", e);
+                onInstallationFailure(session, new PackageManagerException(
+                        SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                        "Staged install failed due to unhandled exception: " + e));
+            }
         }
     }