Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f772d29b authored by Mohammad Samiul Islam's avatar Mohammad Samiul Islam
Browse files

Cleanup how we handle abort of active apex session

- Removed throwing PackageManagerException from abortStagedSession since
it was masking actual failure reason in old code and in this CL, it's no
longer needed.
- Added justification for ignoring failed attempt at aborting active apex
sessions from apexd.
- Created ensureActiveApexSessionIsAborted for reuse. Will be used on
follow up CLs.
- Consolidated cleanup logic execuated on verification failure into
onPreReboootVerificationFailure. Previously, we did not clean up active
apex sessions in apexd in all scenarios.

Bug: 162294757
Test: atest ApexTestCases
Test: atest StagedInstallTest
Change-Id: I58d89be3a1712a51439e222cee4dbb1bec8c28f4
parent 5948d5c2
Loading
Loading
Loading
Loading
+8 −11
Original line number Diff line number Diff line
@@ -270,11 +270,12 @@ public abstract class ApexManager {
    abstract boolean revertActiveSessions();

    /**
     * Abandons the staged session with the given sessionId.
     * Abandons the staged session with the given sessionId. Client should handle {@code false}
     * return value carefully as failure here can leave device in inconsistent state.
     *
     * @return {@code true} upon success, {@code false} if any remote exception occurs
     * @return {@code true} upon success, {@code false} if any exception occurs
     */
    abstract boolean abortStagedSession(int sessionId) throws PackageManagerException;
    abstract boolean abortStagedSession(int sessionId);

    /**
     * Uninstalls given {@code apexPackage}.
@@ -753,17 +754,13 @@ public abstract class ApexManager {
        }

        @Override
        boolean abortStagedSession(int sessionId) throws PackageManagerException {
        boolean abortStagedSession(int sessionId) {
            try {
                waitForApexService().abortStagedSession(sessionId);
                return true;
            } catch (RemoteException re) {
                Slog.e(TAG, "Unable to contact apexservice", re);
                return false;
            } catch (Exception e) {
                throw new PackageManagerException(
                        PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                        "Failed to abort staged session : " + e.getMessage());
                Slog.e(TAG, e.getMessage(), e);
                return false;
            }
        }

@@ -1122,7 +1119,7 @@ public abstract class ApexManager {
        }

        @Override
        boolean abortStagedSession(int sessionId) throws PackageManagerException {
        boolean abortStagedSession(int sessionId) {
            throw new UnsupportedOperationException();
        }

+65 −63
Original line number Diff line number Diff line
@@ -322,9 +322,6 @@ public class StagingManager {
        }
        final long activeVersion = activePackage.applicationInfo.longVersionCode;
        if (activeVersion != session.params.requiredInstalledVersionCode) {
            if (!mApexManager.abortStagedSession(session.sessionId)) {
                Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
            }
            throw new PackageManagerException(
                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                    "Installed version of APEX package " + activePackage.packageName
@@ -338,14 +335,11 @@ public class StagingManager {
            throws PackageManagerException {
        final long activeVersion = activePackage.applicationInfo.longVersionCode;
        final long newVersionCode = newPackage.applicationInfo.longVersionCode;
        boolean isAppDebuggable = (activePackage.applicationInfo.flags
        final boolean isAppDebuggable = (activePackage.applicationInfo.flags
                & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
        final boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
                session.params.installFlags, isAppDebuggable);
        if (activeVersion > newVersionCode && !allowsDowngrade) {
            if (!mApexManager.abortStagedSession(session.sessionId)) {
                Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
            }
            throw new PackageManagerException(
                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                    "Downgrade of APEX package " + newPackage.packageName
@@ -834,37 +828,6 @@ public class StagingManager {
        return null;
    }

    private void verifyApksInSession(PackageInstallerSession session)
            throws PackageManagerException {

        final PackageInstallerSession apksToVerify = extractApksInSession(
                session,  /* preReboot */ true);
        if (apksToVerify == null) {
            return;
        }

        final LocalIntentReceiverAsync receiver = new LocalIntentReceiverAsync(
                (Intent result) -> {
                    int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                            PackageInstaller.STATUS_FAILURE);
                    if (status != PackageInstaller.STATUS_SUCCESS) {
                        final String errorMessage = result.getStringExtra(
                                PackageInstaller.EXTRA_STATUS_MESSAGE);
                        Slog.e(TAG, "Failure to verify APK staged session "
                                + session.sessionId + " [" + errorMessage + "]");
                        session.setStagedSessionFailed(
                                SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage);
                        mPreRebootVerificationHandler.onPreRebootVerificationComplete(
                                session.sessionId);
                        return;
                    }
                    mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
                            session.sessionId);
                });

        apksToVerify.commit(receiver.getIntentSender(), false);
    }

    private void installApksInSession(@NonNull PackageInstallerSession session)
            throws PackageManagerException {

@@ -1041,23 +1004,11 @@ public class StagingManager {

        // A session could be marked ready once its pre-reboot verification ends
        if (session.isStagedSessionReady()) {
            if (sessionContainsApex(session)) {
                try {
                    ApexSessionInfo apexSession =
                            mApexManager.getStagedSessionInfo(session.sessionId);
                    if (apexSession == null || isApexSessionFinalized(apexSession)) {
                        Slog.w(TAG,
                                "Cannot abort session " + session.sessionId
                                        + " because it is not active.");
                    } else {
                        mApexManager.abortStagedSession(session.sessionId);
                    }
                } catch (Exception e) {
                    // Failed to contact apexd service. The apex might still be staged. We can still
            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.w(TAG, "Could not contact apexd to abort staged session " + sessionId);
                }
                Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
            }
        }

@@ -1067,6 +1018,22 @@ public class StagingManager {
        return true;
    }

    /**
     * Ensure that there is no active apex session staged in apexd for the given session.
     *
     * @return returns true if it is ensured that there is no active apex session, otherwise false
     */
    private boolean ensureActiveApexSessionIsAborted(PackageInstallerSession session) {
        if (!sessionContainsApex(session)) {
            return true;
        }
        final ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId);
        if (apexSession == null || isApexSessionFinalized(apexSession)) {
            return true;
        }
        return mApexManager.abortStagedSession(session.sessionId);
    }

    private boolean isApexSessionFinalized(ApexSessionInfo session) {
        /* checking if the session is in a final state, i.e., not active anymore */
        return session.isUnknown || session.isActivationFailed || session.isSuccess
@@ -1347,6 +1314,17 @@ public class StagingManager {
            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
        }

        private void onPreRebootVerificationFailure(PackageInstallerSession 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.setStagedSessionFailed(errorCode, errorMessage);
            onPreRebootVerificationComplete(session.sessionId);
        }

        // Things to do when pre-reboot verification completes for a particular sessionId
        private void onPreRebootVerificationComplete(int sessionId) {
            // Remove it from mVerificationRunning so that verification is considered complete
@@ -1431,8 +1409,7 @@ public class StagingManager {
                        validateApexSignature(apexPackages.get(i));
                    }
                } catch (PackageManagerException e) {
                    session.setStagedSessionFailed(e.error, e.getMessage());
                    onPreRebootVerificationComplete(session.sessionId);
                    onPreRebootVerificationFailure(session, e.error, e.getMessage());
                    return;
                }

@@ -1459,14 +1436,40 @@ public class StagingManager {
            try {
                Slog.d(TAG, "Running a pre-reboot verification for APKs in session "
                        + session.sessionId + " by performing a dry-run install");

                // verifyApksInSession will notify the handler when APK verification is complete
                verifyApksInSession(session);
                // TODO(b/118865310): abort the session on apexd.
            } catch (PackageManagerException e) {
                session.setStagedSessionFailed(e.error, e.getMessage());
                onPreRebootVerificationComplete(session.sessionId);
                onPreRebootVerificationFailure(session, e.error, e.getMessage());
            }
        }

        private void verifyApksInSession(PackageInstallerSession session)
                throws PackageManagerException {

            final PackageInstallerSession apksToVerify = extractApksInSession(
                    session,  /* preReboot */ true);
            if (apksToVerify == null) {
                return;
            }

            final LocalIntentReceiverAsync receiver = new LocalIntentReceiverAsync(
                    (Intent result) -> {
                        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                                PackageInstaller.STATUS_FAILURE);
                        if (status != PackageInstaller.STATUS_SUCCESS) {
                            final String errorMessage = result.getStringExtra(
                                    PackageInstaller.EXTRA_STATUS_MESSAGE);
                            Slog.e(TAG, "Failure to verify APK staged session "
                                    + session.sessionId + " [" + errorMessage + "]");
                            onPreRebootVerificationFailure(session,
                                    SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage);
                            return;
                        }
                        mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
                                session.sessionId);
                    });

            apksToVerify.commit(receiver.getIntentSender(), false);
        }

        /**
@@ -1486,9 +1489,8 @@ public class StagingManager {
            } catch (Exception e) {
                // Failed to get hold of StorageManager
                Slog.e(TAG, "Failed to get hold of StorageManager", e);
                session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
                onPreRebootVerificationFailure(session, SessionInfo.STAGED_SESSION_UNKNOWN,
                        "Failed to get hold of StorageManager");
                onPreRebootVerificationComplete(session.sessionId);
                return;
            }