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

Commit 12bb2d00 authored by Mohammad Samiul Islam's avatar Mohammad Samiul Islam
Browse files

Stop creating extra apk session during post-reboot install phase

Since verification logic has been decoupled from install logic, we can
now create an entry point between the two and directly install the
staged session while skipping the verification process.

Previously we used to create an extra apk-only session with
INSTALL_DISABLE_VERIFICATION flag attached to it.

Bug: 163037460
Test: atest StagedInstallTest StagedInstallInternalTest
Change-Id: I1dcfd897a98c4f518a8947e43540bd58457791be
parent b18d49ad
Loading
Loading
Loading
Loading
+109 −49
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.content.pm.PackageParser.APEX_FILE_EXTENSION;
import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
import static android.system.OsConstants.O_CREAT;
@@ -1458,7 +1459,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                    mChildSessionsRemaining.removeAt(sessionIndex);
                    if (mChildSessionsRemaining.size() == 0) {
                        destroyInternal();
                        dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED,
                        dispatchSessionFinished(INSTALL_SUCCEEDED,
                                "Session installed", null);
                    }
                } else if (PackageInstaller.STATUS_PENDING_USER_ACTION == status) {
@@ -1533,7 +1534,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {

        synchronized (mLock) {
            assertCallerIsOwnerOrRootLocked();
            assertPreparedAndNotDestroyedLocked("commit");
            assertPreparedAndNotDestroyedLocked("commit of session " + sessionId);
            assertNoWriteFileTransfersOpenLocked();

            final boolean isSecureFrpEnabled =
@@ -1663,7 +1664,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            throws PackageManagerException {
        try {
            assertNoWriteFileTransfersOpenLocked();
            assertPreparedAndNotDestroyedLocked("sealing of session");
            assertPreparedAndNotDestroyedLocked("sealing of session " + sessionId);
            mSealed = true;
        } catch (Throwable e) {
            // Convert all exceptions into package manager exceptions as only those are handled
@@ -1701,6 +1702,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    private void onSessionInstallationFailure(int error, String detailedMessage) {
        Slog.e(TAG, "Install of session " + sessionId + " failed: " + detailedMessage);
        destroyInternal();
        dispatchSessionFinished(error, detailedMessage, null);
    }

    private void onStorageHealthStatusChanged(int status) {
        final String packageName = getPackageName();
        if (TextUtils.isEmpty(packageName)) {
@@ -1757,15 +1764,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            try {
                sealLocked();

                // Session that are staged, ready and not multi package will be installed during
                // this boot. As such, we need populate all the fields for successful installation.
                if (isMultiPackage()) {
                    return;
                }
                final PackageInstallerSession root = hasParentSessionId()
                        ? allSessions.get(getParentSessionId())
                        : this;
                if (root != null && root.isStagedSessionReady()) {
                    if (isApexSession()) {
                    // APEX installations rely on certain fields to be populated after reboot.
                    // E.g. mPackageName.
                        validateApexInstallLocked();
                    } else {
                    // Populate mPackageName for this APK session which is required by the staging
                    // manager to check duplicate apk-in-apex.
                    PackageInstallerSession parent = allSessions.get(mParentSessionId);
                    if (parent != null && parent.isStagedSessionReady()) {
                        validateApkInstallLocked();
                    }
                }
@@ -1829,7 +1839,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            mStagingManager.commitSession(this);
            // TODO(b/136257624): CTS test fails if we don't send session finished broadcast, even
            //  though ideally, we just need to send session committed broadcast.
            dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED, "Session staged", null);
            dispatchSessionFinished(INSTALL_SUCCEEDED, "Session staged", null);
            return;
        }

@@ -1911,12 +1921,53 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    /**
     * Installs apks of staged session while skipping the verification process for a committed and
     * ready session.
     */
    void installStagedSession(IntentSender statusReceiver) {
        assertCallerIsOwnerOrRootOrSystemLocked();
        Preconditions.checkArgument(!hasParentSessionId()); // Don't allow installing child sessions
        Preconditions.checkArgument(isCommitted() && isStagedSessionReady());

        // 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();
    }

    private void updateRemoteStatusReceiver(IntentSender remoteStatusReceiver) {
        synchronized (mLock) {
            mRemoteStatusReceiver = remoteStatusReceiver;
            if (isMultiPackage()) {
                final IntentSender childIntentSender =
                        new ChildStatusIntentReceiver(mChildSessions.clone(), remoteStatusReceiver)
                                .getIntentSender();
                for (int i = mChildSessions.size() - 1; i >= 0; --i) {
                    mChildSessions.valueAt(i).mRemoteStatusReceiver = childIntentSender;
                }
            }
        }
    }

    private void install() {
        try {
            installNonStaged();
        } catch (PackageManagerException e) {
            final String completeMsg = ExceptionUtils.getCompleteMessage(e);
            onSessionInstallationFailure(e.error, completeMsg);
        }
    }

    private void installNonStaged()
            throws PackageManagerException {
        final PackageManagerService.InstallParams installingSession =
                makeInstallParams();
        Preconditions.checkArgument(containsApkSession());

        final PackageManagerService.InstallParams installingSession = makeInstallParams();
        if (installingSession == null) {
            return;
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                    "Session should contain at least one apk session for installation");
        }
        if (isMultiPackage()) {
            final List<PackageInstallerSession> childSessions;
@@ -2084,7 +2135,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                @Override
                public void onPackageInstalled(String basePackageName, int returnCode, String msg,
                        Bundle extras) {
                    if (returnCode == PackageManager.INSTALL_SUCCEEDED) {
                    if (returnCode == INSTALL_SUCCEEDED) {
                        onVerificationComplete();
                    } else {
                        onSessionVerificationFailure(returnCode, msg);
@@ -2126,20 +2177,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            return;
        }

        try {
            installNonStaged();
        } catch (PackageManagerException e) {
            final String completeMsg = ExceptionUtils.getCompleteMessage(e);
            Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
            destroyInternal();
            dispatchSessionFinished(e.error, completeMsg, null);
        }
        install();
    }

    /**
     * Stages this session for install and returns a
     * {@link PackageManagerService.InstallParams} representing this new staged state.
     */
    @Nullable
    private PackageManagerService.InstallParams makeInstallParams()
            throws PackageManagerException {
        synchronized (mLock) {
@@ -2153,8 +2198,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            }
        }

        // We've reached point of no return; call into PMS to install the stage.
        // Regardless of success or failure we always destroy session.
        // Do not try to install apex session. Parent session will have at least one apk session.
        if (!isMultiPackage() && isApexSession()) {
            sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED,
                    "Apex package should have been installed by apexd", null);
            return null;
        }

        final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
            @Override
            public void onUserActionRequired(Intent intent) {
@@ -2164,9 +2214,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            @Override
            public void onPackageInstalled(String basePackageName, int returnCode, String msg,
                    Bundle extras) {
                if (isStaged()) {
                    sendUpdateToRemoteStatusReceiver(returnCode, msg, extras);
                } else {
                    // We've reached point of no return; call into PMS to install the stage.
                    // Regardless of success or failure we always destroy session.
                    destroyInternal();
                    dispatchSessionFinished(returnCode, msg, extras);
                }
            }
        };

        final UserHandle user;
@@ -2952,7 +3008,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            handle = NativeLibraryHelper.Handle.create(packageDir);
            final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir,
                    abiOverride, isIncrementalInstallation());
            if (res != PackageManager.INSTALL_SUCCEEDED) {
            if (res != INSTALL_SUCCEEDED) {
                throw new PackageManagerException(res,
                        "Failed to extract native libraries, res=" + res);
            }
@@ -3586,16 +3642,35 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    }

    private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
        final IntentSender statusReceiver;
        final String packageName;
        sendUpdateToRemoteStatusReceiver(returnCode, msg, extras);

        synchronized (mLock) {
            mFinalStatus = returnCode;
            mFinalMessage = msg;
        }

        final boolean success = (returnCode == INSTALL_SUCCEEDED);

        // Send broadcast to default launcher only if it's a new install
        // TODO(b/144270665): Secure the usage of this broadcast.
        final boolean isNewInstall = extras == null || !extras.getBoolean(Intent.EXTRA_REPLACING);
        if (success && isNewInstall && mPm.mInstallerService.okToSendBroadcasts()) {
            mPm.sendSessionCommitBroadcast(generateInfoScrubbed(true /*icon*/), userId);
        }

        mCallback.onSessionFinished(this, success);
        if (isDataLoaderInstallation()) {
            logDataLoaderInstallationSession(returnCode);
        }
    }

    private void sendUpdateToRemoteStatusReceiver(int returnCode, String msg, Bundle extras) {
        final IntentSender statusReceiver;
        final String packageName;
        synchronized (mLock) {
            statusReceiver = mRemoteStatusReceiver;
            packageName = mPackageName;
        }

        if (statusReceiver != null) {
            // Execute observer.onPackageInstalled on different thread as we don't want callers
            // inside the system server have to worry about catching the callbacks while they are
@@ -3606,23 +3681,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            args.arg3 = extras;
            args.arg4 = statusReceiver;
            args.argi1 = returnCode;

            mHandler.obtainMessage(MSG_ON_PACKAGE_INSTALLED, args).sendToTarget();
        }

        final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);

        // Send broadcast to default launcher only if it's a new install
        // TODO(b/144270665): Secure the usage of this broadcast.
        final boolean isNewInstall = extras == null || !extras.getBoolean(Intent.EXTRA_REPLACING);
        if (success && isNewInstall && mPm.mInstallerService.okToSendBroadcasts()) {
            mPm.sendSessionCommitBroadcast(generateInfoScrubbed(true /*icon*/), userId);
        }

        mCallback.onSessionFinished(this, success);
        if (isDataLoaderInstallation()) {
            logDataLoaderInstallationSession(returnCode);
        }
    }

    /** {@hide} */
@@ -3834,7 +3894,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    private static void sendOnPackageInstalled(Context context, IntentSender target, int sessionId,
            boolean showNotification, int userId, String basePackageName, int returnCode,
            String msg, Bundle extras) {
        if (PackageManager.INSTALL_SUCCEEDED == returnCode && showNotification) {
        if (INSTALL_SUCCEEDED == returnCode && showNotification) {
            boolean update = (extras != null) && extras.getBoolean(Intent.EXTRA_REPLACING);
            Notification notification = PackageInstallerService.buildSuccessNotification(context,
                    context.getResources()
+5 −5
Original line number Diff line number Diff line
@@ -794,18 +794,18 @@ public class StagingManager {

    private void installApksInSession(PackageInstallerSession session)
            throws PackageManagerException {
        final PackageInstallerSession apksToInstall = extractApksInSession(session);
        if (apksToInstall == null) {
        if (!session.containsApkSession()) {
            return;
        }

        if ((apksToInstall.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
        if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
            // If rollback is available for this session, notify the rollback
            // manager of the apk session so it can properly enable rollback.
            final RollbackManagerInternal rm =
                    LocalServices.getService(RollbackManagerInternal.class);
            try {
                rm.notifyStagedApkSession(session.sessionId, apksToInstall.sessionId);
                // TODO(b/136257624): extra apk session id in rollback is now redundant.
                rm.notifyStagedApkSession(session.sessionId, session.sessionId);
            } catch (RuntimeException re) {
                Slog.e(TAG, "Failed to notifyStagedApkSession for session: "
                        + session.sessionId, re);
@@ -813,7 +813,7 @@ public class StagingManager {
        }

        final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync();
        apksToInstall.commit(receiver.getIntentSender(), false);
        session.installStagedSession(receiver.getIntentSender());
        final Intent result = receiver.getResult();
        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                PackageInstaller.STATUS_FAILURE);