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

Commit b522c02e authored by Mohammad Samiul Islam's avatar Mohammad Samiul Islam Committed by Android (Google) Code Review
Browse files

Merge changes from topic "app-staging"

* changes:
  Reverify certain conditions during staged install
  Ensure staged sessions are not installed in app-staging directory
  Stop creating extra apk session during post-reboot install phase
parents dcd5a9cb 038fd8d0
Loading
Loading
Loading
Loading
+114 −49
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ 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_STAGED;
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 +1460,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 +1535,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {

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

            final boolean isSecureFrpEnabled =
@@ -1663,7 +1665,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 +1703,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 +1765,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 +1840,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 +1922,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 +2136,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 +2178,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 +2199,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 +2215,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;
@@ -2176,6 +2233,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            user = new UserHandle(userId);
        }

        if (params.isStaged) {
            params.installFlags |= INSTALL_STAGED;
        }

        synchronized (mLock) {
            return mPm.new InstallParams(stageDir, localObserver, params, mInstallSource, user,
                    mSigningDetails, mInstallerUid);
@@ -2952,7 +3013,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 +3647,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 +3686,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 +3899,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()
+81 −51
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTEN
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
@@ -14990,6 +14991,7 @@ public class PackageManagerService extends IPackageManager.Stub
        @Nullable MultiPackageInstallParams mParentInstallParams;
        final boolean forceQueryableOverride;
        final int mDataLoaderType;
        final long requiredInstalledVersionCode;
        InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer,
                int installFlags, InstallSource installSource, String volumeUuid,
@@ -15010,6 +15012,7 @@ public class PackageManagerService extends IPackageManager.Stub
            this.installReason = PackageManager.INSTALL_REASON_UNKNOWN;
            this.forceQueryableOverride = false;
            this.mDataLoaderType = DataLoaderType.NONE;
            this.requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
        }
        InstallParams(File stagedDir, IPackageInstallObserver2 observer,
@@ -15032,6 +15035,7 @@ public class PackageManagerService extends IPackageManager.Stub
            forceQueryableOverride = sessionParams.forceQueryableOverride;
            mDataLoaderType = (sessionParams.dataLoaderParams != null)
                    ? sessionParams.dataLoaderParams.getType() : DataLoaderType.NONE;
            requiredInstalledVersionCode = sessionParams.requiredInstalledVersionCode;
        }
        @Override
@@ -15178,6 +15182,18 @@ public class PackageManagerService extends IPackageManager.Stub
        public void handleStartCopy() {
            PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
                    origin.resolvedPath, installFlags, packageAbiOverride);
            // For staged session, there is a delay between its verification and install. Device
            // state can change within this delay and hence we need to re-verify certain conditions.
            boolean isStaged = (installFlags & INSTALL_STAGED) != 0;
            if (isStaged) {
                mRet = verifyReplacingVersionCode(
                        pkgLite, requiredInstalledVersionCode, installFlags);
                if (mRet != INSTALL_SUCCEEDED) {
                    return;
                }
            }
            mRet = overrideInstallLocation(pkgLite);
        }
@@ -15324,11 +15340,14 @@ public class PackageManagerService extends IPackageManager.Stub
            PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
                    origin.resolvedPath, installFlags, packageAbiOverride);
            mRet = verifyReplacingVersionCode(pkgLite);
            mRet = verifyReplacingVersionCode(pkgLite, requiredInstalledVersionCode, installFlags);
            if (mRet != INSTALL_SUCCEEDED) {
                return;
            }
            // Perform package verification and enable rollback (unless we are simply moving the
            // package).
            if (mRet == INSTALL_SUCCEEDED && !origin.existing) {
            if (!origin.existing) {
                sendApkVerificationRequest(pkgLite);
                if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
                    sendEnableRollbackRequest();
@@ -15336,54 +15355,6 @@ public class PackageManagerService extends IPackageManager.Stub
            }
        }
        private int verifyReplacingVersionCode(PackageInfoLite pkgLite) {
            String packageName = pkgLite.packageName;
            synchronized (mLock) {
                // Package which currently owns the data that the new package will own if installed.
                // If an app is uninstalled while keeping data (e.g. adb uninstall -k), installedPkg
                // will be null whereas dataOwnerPkg will contain information about the package
                // which was uninstalled while keeping its data.
                AndroidPackage dataOwnerPkg = mPackages.get(packageName);
                if (dataOwnerPkg  == null) {
                    PackageSetting ps = mSettings.mPackages.get(packageName);
                    if (ps != null) {
                        dataOwnerPkg = ps.pkg;
                    }
                }
                if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
                    if (dataOwnerPkg == null) {
                        Slog.w(TAG, "Required installed version code was "
                                + requiredInstalledVersionCode
                                + " but package is not installed");
                        return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
                    }
                    if (dataOwnerPkg.getLongVersionCode() != requiredInstalledVersionCode) {
                        Slog.w(TAG, "Required installed version code was "
                                + requiredInstalledVersionCode
                                + " but actual installed version is "
                                + dataOwnerPkg.getLongVersionCode());
                        return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
                    }
                }
                if (dataOwnerPkg != null) {
                    if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
                            dataOwnerPkg.isDebuggable())) {
                        try {
                            checkDowngrade(dataOwnerPkg, pkgLite);
                        } catch (PackageManagerException e) {
                            Slog.w(TAG, "Downgrade detected: " + e.getMessage());
                            return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
                        }
                    }
                }
            }
            return PackageManager.INSTALL_SUCCEEDED;
        }
        void sendApkVerificationRequest(PackageInfoLite pkgLite) {
            final int verificationId = mPendingVerificationToken++;
@@ -15977,7 +15948,7 @@ public class PackageManagerService extends IPackageManager.Stub
                return false;
            }
            final File targetDir = codeFile.getParentFile();
            final File targetDir = resolveTargetDir();
            final File beforeCodeFile = codeFile;
            final File afterCodeFile = getNextCodePath(targetDir, parsedPackage.getPackageName());
@@ -16020,6 +15991,17 @@ public class PackageManagerService extends IPackageManager.Stub
            return true;
        }
        // TODO(b/168126411): Once staged install flow starts using the same folder as non-staged
        //  flow, we won't need this method anymore.
        private File resolveTargetDir() {
            boolean isStagedInstall = (installFlags & INSTALL_STAGED) != 0;
            if (isStagedInstall) {
                return Environment.getDataAppDirectory(null);
            } else {
                return codeFile.getParentFile();
            }
        }
        int doPostInstall(int status, int uid) {
            if (status != PackageManager.INSTALL_SUCCEEDED) {
                cleanUp();
@@ -24197,6 +24179,54 @@ public class PackageManagerService extends IPackageManager.Stub
        }
    }
    private int verifyReplacingVersionCode(PackageInfoLite pkgLite,
            long requiredInstalledVersionCode, int installFlags) {
        String packageName = pkgLite.packageName;
        synchronized (mLock) {
            // Package which currently owns the data that the new package will own if installed.
            // If an app is uninstalled while keeping data (e.g. adb uninstall -k), installedPkg
            // will be null whereas dataOwnerPkg will contain information about the package
            // which was uninstalled while keeping its data.
            AndroidPackage dataOwnerPkg = mPackages.get(packageName);
            if (dataOwnerPkg  == null) {
                PackageSetting ps = mSettings.mPackages.get(packageName);
                if (ps != null) {
                    dataOwnerPkg = ps.pkg;
                }
            }
            if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
                if (dataOwnerPkg == null) {
                    Slog.w(TAG, "Required installed version code was "
                            + requiredInstalledVersionCode
                            + " but package is not installed");
                    return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
                }
                if (dataOwnerPkg.getLongVersionCode() != requiredInstalledVersionCode) {
                    Slog.w(TAG, "Required installed version code was "
                            + requiredInstalledVersionCode
                            + " but actual installed version is "
                            + dataOwnerPkg.getLongVersionCode());
                    return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
                }
            }
            if (dataOwnerPkg != null) {
                if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
                        dataOwnerPkg.isDebuggable())) {
                    try {
                        checkDowngrade(dataOwnerPkg, pkgLite);
                    } catch (PackageManagerException e) {
                        Slog.w(TAG, "Downgrade detected: " + e.getMessage());
                        return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
                    }
                }
            }
        }
        return PackageManager.INSTALL_SUCCEEDED;
    }
    /**
     * Check and throw if the given before/after packages would be considered a
     * downgrade.
+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);