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

Commit c271def0 authored by Chun-Wei Wang's avatar Chun-Wei Wang Committed by Android (Google) Code Review
Browse files

Merge "Apply APPLIED/FAILED to non-staged sessions (8/n)"

parents 68b0ca05 a49b7c68
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -1650,7 +1650,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
        public void onSessionChanged(PackageInstallerSession session) {
            session.markUpdated();
            mSettingsWriteRequest.schedule();
            if (mOkToSendBroadcasts && !session.isDestroyed()) {
            // TODO(b/210359798): Remove the session.isStaged() check. Some apps assume this
            // broadcast is sent by only staged sessions and call isStagedSessionApplied() without
            // checking if it is a staged session or not and cause exception.
            if (mOkToSendBroadcasts && !session.isDestroyed() && session.isStaged()) {
                // we don't scrub the data here as this is sent only to the installer several
                // privileged system packages
                sendSessionUpdatedBroadcast(
+83 −68
Original line number Diff line number Diff line
@@ -182,6 +182,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
@@ -549,18 +550,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        /**
         * Installs apks of staged session while skipping the verification process for a committed
         * and ready session.
         *
         * @return a CompletableFuture that will be completed when installation completes.
         */
        @Override
        public void installSession(IntentSender statusReceiver) {
        public CompletableFuture<Void> installSession() {
            assertCallerIsOwnerOrRootOrSystem();
            assertNotChild("StagedSession#installSession");
            Preconditions.checkArgument(isCommitted() && isSessionReady());

            // Since staged sessions are installed during boot, the original reference to status
            // receiver from the owner has already been lost. We can safely replace it with a
            // status receiver from the system without effecting the flow.
            updateRemoteStatusReceiver(statusReceiver);
            install();
            return install();
        }

        private void updateRemoteStatusReceiver(IntentSender remoteStatusReceiver) {
@@ -1694,15 +1692,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
        if (isMultiPackage()) {
            synchronized (mLock) {
                final IntentSender childIntentSender =
                        new ChildStatusIntentReceiver(mChildSessions.clone(), statusReceiver)
                                .getIntentSender();
                boolean sealFailed = false;
                for (int i = mChildSessions.size() - 1; i >= 0; --i) {
                    // seal all children, regardless if any of them fail; we'll throw/return
                    // as appropriate once all children have been processed
                    if (!mChildSessions.valueAt(i)
                            .markAsSealed(childIntentSender, forTransfer)) {
                    if (!mChildSessions.valueAt(i).markAsSealed(null, forTransfer)) {
                        sealFailed = true;
                    }
                }
@@ -1890,8 +1884,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
     * This method may be called multiple times to update the status receiver validate caller
     * permissions.
     */
    private boolean markAsSealed(@NonNull IntentSender statusReceiver, boolean forTransfer) {
        Objects.requireNonNull(statusReceiver);
    private boolean markAsSealed(@Nullable IntentSender statusReceiver, boolean forTransfer) {
        Preconditions.checkState(statusReceiver != null || hasParentSessionId(),
                "statusReceiver can't be null for the root session");
        assertCallerIsOwnerOrRoot();

        synchronized (mLock) {
@@ -2191,7 +2186,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    }

    @WorkerThread
    private static boolean checkUserActionRequirement(PackageInstallerSession session) {
    private static boolean checkUserActionRequirement(
            PackageInstallerSession session, IntentSender target) {
        if (session.isMultiPackage()) {
            return false;
        }
@@ -2200,7 +2196,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        // TODO(b/159331446): Move this to makeSessionActiveForInstall and update javadoc
        userActionRequirement = session.computeUserActionRequirement();
        if (userActionRequirement == USER_ACTION_REQUIRED) {
            session.sendPendingUserActionIntent();
            session.sendPendingUserActionIntent(target);
            return true;
        }

@@ -2212,7 +2208,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {

            if (validatedTargetSdk != INVALID_TARGET_SDK_VERSION
                    && validatedTargetSdk < Build.VERSION_CODES.Q) {
                session.sendPendingUserActionIntent();
                session.sendPendingUserActionIntent(target);
                return true;
            }

@@ -2221,7 +2217,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                        session.getInstallerPackageName(), session.getPackageName())) {
                    // Fall back to the non-silent update if a repeated installation is invoked
                    // within the throttle time.
                    session.sendPendingUserActionIntent();
                    session.sendPendingUserActionIntent(target);
                    return true;
                }
                session.mSilentUpdatePolicy.track(session.getInstallerPackageName(),
@@ -2241,7 +2237,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    private boolean sendPendingUserActionIntentIfNeeded() {
        assertNotChild("PackageInstallerSession#sendPendingUserActionIntentIfNeeded");

        return sessionContains(PackageInstallerSession::checkUserActionRequirement);
        final IntentSender statusReceiver = getRemoteStatusReceiver();
        return sessionContains(s -> checkUserActionRequirement(s, statusReceiver));
    }

    @WorkerThread
@@ -2439,53 +2436,69 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        });
    }

    private void install() {
        try {
            installNonStaged();
        } catch (PackageManagerException e) {
            final String completeMsg = ExceptionUtils.getCompleteMessage(e);
            onSessionInstallationFailure(e.error, completeMsg);
    /**
     * Stages installs and do cleanup accordingly depending on whether the installation is
     * successful or not.
     *
     * @return a future that will be completed when the whole process is completed.
     */
    private CompletableFuture<Void> install() {
        return installNonStaged().whenComplete((r, t) -> {
            if (t == null) {
                setSessionApplied();
                dispatchSessionFinished(INSTALL_SUCCEEDED, "Session installed", null);
                maybeFinishChildSessions(INSTALL_SUCCEEDED, "Session installed");
            } else {
                PackageManagerException e = (PackageManagerException) t.getCause();
                setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED,
                        PackageManager.installStatusToString(e.error, e.getMessage()));
                dispatchSessionFinished(e.error, e.getMessage(), null);
                maybeFinishChildSessions(e.error, e.getMessage());
            }
        });
    }

    private void installNonStaged()
            throws PackageManagerException {
        final InstallParams installingSession = makeInstallParams();
        if (installingSession == null) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                    "Session should contain at least one apk session for installation");
        }
    /**
     * Stages sessions (including child sessions if any) for install.
     *
     * @return a future that will be completed when the whole session is completed (could be
     *         success or failure).
     */
    private CompletableFuture<Void> installNonStaged() {
        try {
            List<CompletableFuture<Void>> futures = new ArrayList<>();
            CompletableFuture<Void> future = new CompletableFuture<>();
            futures.add(future);
            final InstallParams installingSession = makeInstallParams(future);
            if (isMultiPackage()) {
                final List<PackageInstallerSession> childSessions = getChildSessions();
                List<InstallParams> installingChildSessions = new ArrayList<>(childSessions.size());
            boolean success = true;
            PackageManagerException failure = null;
                for (int i = 0; i < childSessions.size(); ++i) {
                    final PackageInstallerSession session = childSessions.get(i);
                try {
                    final InstallParams installingChildSession =
                            session.makeInstallParams();
                    future = new CompletableFuture<>();
                    futures.add(future);
                    final InstallParams installingChildSession = session.makeInstallParams(future);
                    if (installingChildSession != null) {
                        installingChildSessions.add(installingChildSession);
                    }
                } catch (PackageManagerException e) {
                    failure = e;
                    success = false;
                }
            }
            if (!success) {
                sendOnPackageInstalled(mContext, getRemoteStatusReceiver(), sessionId,
                        isInstallerDeviceOwnerOrAffiliatedProfileOwner(), userId, null,
                        failure.error, failure.getLocalizedMessage(), null);
                return;
                }
                if (!installingChildSessions.isEmpty()) {
                    installingSession.installStage(installingChildSessions);
        } else {
                }
            } else if (installingSession != null) {
                installingSession.installStage();
            }

            CompletableFuture<Void>[] arr = new CompletableFuture[futures.size()];
            return CompletableFuture.allOf(futures.toArray(arr));
        } catch (PackageManagerException e) {
            CompletableFuture<Void> future = new CompletableFuture<>();
            future.completeExceptionally(e);
            return future;
        }
    }

    private void sendPendingUserActionIntent() {
    private void sendPendingUserActionIntent(IntentSender target) {
        // User needs to confirm installation;
        // give installer an intent they can use to involve
        // user.
@@ -2493,7 +2506,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        intent.setPackage(mPm.getPackageInstallerPackageName());
        intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);

        sendOnUserActionRequired(mContext, getRemoteStatusReceiver(), sessionId, intent);
        sendOnUserActionRequired(mContext, target, sessionId, intent);

        // Commit was keeping session marked as active until now; release
        // that extra refcount so session appears idle.
@@ -2525,9 +2538,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    /**
     * Stages this session for install and returns a
     * {@link InstallParams} representing this new staged state.
     *
     * @param future a future that will be completed when this session is completed.
     */
    @Nullable
    private InstallParams makeInstallParams()
    private InstallParams makeInstallParams(CompletableFuture<Void> future)
            throws PackageManagerException {
        synchronized (mLock) {
            if (mDestroyed) {
@@ -2540,11 +2555,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            }
        }

        // Do not try to install staged apex session. Parent session will have at least one apk
        // session.
        if (!isMultiPackage() && isApexSession() && params.isStaged) {
            dispatchSessionFinished(INSTALL_SUCCEEDED,
                    "Apex package should have been installed by apexd", null);
        if (isMultiPackage()) {
            // Always treat parent session as success for it has nothing to install
            future.complete(null);
        } else if (isApexSession() && params.isStaged) {
            // Staged apex sessions have been handled by apexd
            future.complete(null);
            return null;
        }

@@ -2557,12 +2573,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            @Override
            public void onPackageInstalled(String basePackageName, int returnCode, String msg,
                    Bundle extras) {
                if (!isStaged()) {
                    // We've reached point of no return; call into PMS to install the stage.
                    // Regardless of success or failure we always destroy session.
                    destroyInternal();
                if (returnCode == INSTALL_SUCCEEDED) {
                    future.complete(null);
                } else {
                    future.completeExceptionally(new PackageManagerException(returnCode, msg));
                }
                dispatchSessionFinished(returnCode, msg, extras);
            }
        };

+13 −21
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
@@ -131,7 +132,7 @@ public class StagingManager {
        void setSessionReady();
        void setSessionFailed(@SessionErrorCode int errorCode, String errorMessage);
        void setSessionApplied();
        void installSession(IntentSender statusReceiver);
        CompletableFuture<Void> installSession();
        boolean hasParentSessionId();
        long getCommittedMillis();
        void abandon();
@@ -417,8 +418,6 @@ public class StagingManager {
        installApksInSession(session);
        t.traceEnd();

        Slog.d(TAG, "Marking session " + session.sessionId() + " as applied");
        session.setSessionApplied();
        if (hasApex) {
            if (supportsCheckpoint) {
                // Store the session ID, which will be marked as successful by ApexManager upon
@@ -494,24 +493,17 @@ public class StagingManager {
        }
    }

    private void installApksInSession(StagedSession session)
            throws PackageManagerException {
        if (!session.containsApkSession()) {
            return;
        }

        final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync();
        session.installSession(receiver.getIntentSender());
        final Intent result = receiver.getResult();
        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 install APK staged session "
                    + session.sessionId() + " [" + errorMessage + "]");
            throw new PackageManagerException(
                    SessionInfo.SESSION_ACTIVATION_FAILED, errorMessage);
    private void installApksInSession(StagedSession session) throws PackageManagerException {
        try {
            // Blocking wait for installation to complete
            session.installSession().get();
        } catch (InterruptedException e) {
            // Should be impossible
            throw new RuntimeException(e);
        } catch (ExecutionException ee) {
            PackageManagerException e = (PackageManagerException) ee.getCause();
            final String errorMsg = PackageManager.installStatusToString(e.error, e.getMessage());
            throw new PackageManagerException(SessionInfo.SESSION_ACTIVATION_FAILED, errorMsg);
        }
    }

+2 −2
Original line number Diff line number Diff line
@@ -36,7 +36,6 @@ import android.apex.ApexInfo;
import android.apex.ApexSessionInfo;
import android.apex.ApexSessionParams;
import android.content.Context;
import android.content.IntentSender;
import android.content.pm.ApexStagedEvent;
import android.content.pm.IStagedApexObserver;
import android.content.pm.PackageInstaller;
@@ -75,6 +74,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;

@Presubmit
@@ -953,7 +953,7 @@ public class StagingManagerTest {
        }

        @Override
        public void installSession(IntentSender statusReceiver) {
        public CompletableFuture<Void> installSession() {
            throw new UnsupportedOperationException();
        }