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

Commit d9b65326 authored by JW Wang's avatar JW Wang
Browse files

Merge abandon() methods (14/n)

Unify the code flow of abandon() since non-staged sessions now also
have READY/APPLIED/FAILED states.

1. set mStageDirInUse to true to prevent deleting staging files
   when verification is in progress.
2. After verification, process pending abandon() request if any
   and abort the install process.
3. set mStageDirInUse to true again when install phase is about
   to start.

Bug: 210359798
Test: atest CtsRootPackageInstallerHostTestCases \
            CtsAtomicInstallTestCases
Change-Id: I2b2e417ba09304daad5edee03e5717a414d4c023
parent 88ada468
Loading
Loading
Loading
Loading
+81 −103
Original line number Diff line number Diff line
@@ -469,8 +469,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    @Nullable
    final StagedSession mStagedSession;

    @VisibleForTesting
    public class StagedSession implements StagingManager.StagedSession {
    /**
     * 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.
@@ -479,6 +477,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    @Nullable
    private Runnable mPendingAbandonCallback;

    @VisibleForTesting
    public class StagedSession implements StagingManager.StagedSession {
        @Override
        public List<StagingManager.StagedSession> getChildSessions() {
            if (!params.isMultiPackage) {
@@ -575,9 +575,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {

        @Override
        public boolean isInTerminalState() {
            synchronized (mLock) {
                return mSessionApplied || mSessionFailed;
            }
            return PackageInstallerSession.this.isInTerminalState();
        }

        @Override
@@ -612,48 +610,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {

        @Override
        public void abandon() {
            final Runnable r;
            synchronized (mLock) {
                assertNotChild("StagedSession#abandon");
                assertCallerIsOwnerOrRootOrSystem();
                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;
                r = () -> {
                    assertNotLocked("abandonStaged");
                    if (mCommitted.get()) {
                        mStagingManager.abortCommittedSession(this);
                    }
                    destroy();
                    dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
                    maybeFinishChildSessions(INSTALL_FAILED_ABORTED,
                            "Session was abandoned because the parent session is abandoned");
                };
                if (mStageDirInUse) {
                    // Pre-reboot verification is ongoing, not safe to clean up the session yet.
                    mPendingAbandonCallback = r;
                    mCallback.onSessionChanged(PackageInstallerSession.this);
                    return;
                }
            }
            r.run();
        }

        /**
         * Called when pre-reboot verification has ended.
         * Now it is safe to clean up the session if {@link #abandon()} has been called previously.
         */
        private void notifyEndPreRebootVerification() {
            synchronized (mLock) {
                Preconditions.checkState(mStageDirInUse);
                mStageDirInUse = false;
            }
            dispatchPendingAbandonCallback();
            PackageInstallerSession.this.abandon();
        }

        /**
@@ -669,17 +626,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            Preconditions.checkArgument(!isInTerminalState());
            verify();
        }

        private void dispatchPendingAbandonCallback() {
            final Runnable callback;
            synchronized (mLock) {
                callback = mPendingAbandonCallback;
                mPendingAbandonCallback = null;
            }
            if (callback != null) {
                callback.run();
            }
        }
    }

    /**
@@ -1138,9 +1084,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    private boolean isInTerminalState() {
        synchronized (mLock) {
            return mSessionApplied || mSessionFailed;
        }
    }

    /** Returns true if a staged session has reached a final state and can be forgotten about  */
    public boolean isStagedAndInTerminalState() {
        return params.isStaged && mStagedSession.isInTerminalState();
        return params.isStaged && isInTerminalState();
    }

    private void assertNotLocked(String cookie) {
@@ -1968,9 +1920,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {

    private void onSessionVerificationFailure(int error, String msg) {
        Slog.e(TAG, "Failed to verify session " + sessionId);
        if (isStaged()) {
            mStagedSession.notifyEndPreRebootVerification();
        }
        // Dispatch message to remove session from PackageInstallerService.
        dispatchSessionFinished(error, msg, null);
        maybeFinishChildSessions(error, msg);
@@ -2279,6 +2228,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    @GuardedBy("mLock")
    private void markStageDirInUseLocked() throws PackageManagerException {
        if (mDestroyed) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                    "Session destroyed");
        }
        // Set this flag to prevent abandon() from deleting staging files when verification or
        // installation is about to start.
        mStageDirInUse = true;
    }

    private void parseApkAndExtractNativeLibraries() throws PackageManagerException {
        synchronized (mLock) {
            if (mStageDirInUse) {
@@ -2324,19 +2284,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    private void verifyNonStaged()
            throws PackageManagerException {
        synchronized (mLock) {
            if (mDestroyed) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                        "Session destroyed");
            }
            // Set this flag to prevent abandon() from deleting staging files while verification is
            // in progress. For staged sessions, we will reset this flag when verification is done
            // so abandon() can take effect. For non-staged sessions, the staging files will be
            // deleted when install is completed (no matter success or not). No need to reset
            // the flag.
            mStageDirInUse = true;
            markStageDirInUseLocked();
        }
        mSessionProvider.getSessionVerifier().verify(this, (error, msg) -> {
            mHandler.post(() -> {
                if (dispatchPendingAbandonCallback()) {
                    // No need to continue if abandoned
                    return;
                }
                if (error == INSTALL_SUCCEEDED) {
                    onVerificationComplete();
                } else {
@@ -2426,7 +2381,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    @WorkerThread
    private void onVerificationComplete() {
        if (isStaged()) {
            mStagedSession.notifyEndPreRebootVerification();
            mStagingManager.commitSession(mStagedSession);
            sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged", null);
            return;
@@ -2444,14 +2398,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    private InstallParams makeInstallParams(CompletableFuture<Void> future)
            throws PackageManagerException {
        synchronized (mLock) {
            if (mDestroyed) {
                throw new PackageManagerException(
                        INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
            }
            if (!mSealed) {
                throw new PackageManagerException(
                        INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
            }
            markStageDirInUseLocked();
        }

        if (isMultiPackage()) {
@@ -3523,36 +3474,63 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    private void abandonNonStaged() {
    private void assertNotChild(String cookie) {
        if (hasParentSessionId()) {
            throw new IllegalStateException(cookie + " can't be called on a child session, id="
                    + sessionId + " parentId=" + getParentSessionId());
        }
    }

    /**
     * Called when verification has completed. Now it is safe to clean up the session
     * if {@link #abandon()} has been called previously.
     *
     * @return True if this session has been abandoned.
     */
    private boolean dispatchPendingAbandonCallback() {
        final Runnable callback;
        synchronized (mLock) {
            Preconditions.checkState(mStageDirInUse);
            mStageDirInUse = false;
            callback = mPendingAbandonCallback;
            mPendingAbandonCallback = null;
        }
        if (callback != null) {
            callback.run();
            return true;
        }
        return false;
    }

    @Override
    public void abandon() {
        final Runnable r;
        synchronized (mLock) {
            assertNotChild("abandonNonStaged");
            assertNotChild("abandon");
            assertCallerIsOwnerOrRootOrSystem();
            if (mStageDirInUse) {
                if (LOGD) Slog.d(TAG, "Ignoring abandon for staging files are in use");
            if (isInTerminalState()) {
                // Finalized sessions have been properly cleaned up. No need to abandon them.
                return;
            }
            mDestroyed = true;
            r = () -> {
                assertNotLocked("abandonStaged");
                if (isStaged() && mCommitted.get()) {
                    mStagingManager.abortCommittedSession(mStagedSession);
                }
                destroy();
                dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
                maybeFinishChildSessions(INSTALL_FAILED_ABORTED,
                        "Session was abandoned because the parent session is abandoned");
            };
            if (mStageDirInUse) {
                // Verification is ongoing, not safe to clean up the session yet.
                mPendingAbandonCallback = r;
                mCallback.onSessionChanged(this);
                return;
            }

    private void assertNotChild(String cookie) {
        if (hasParentSessionId()) {
            throw new IllegalStateException(cookie + " can't be called on a child session, id="
                    + sessionId + " parentId=" + getParentSessionId());
        }
    }

    @Override
    public void abandon() {
        if (params.isStaged) {
            mStagedSession.abandon();
        } else {
            abandonNonStaged();
        }
        r.run();
    }

    @Override