From 12bb2d006151a20aaf6c7ffc1d364efba55a793a Mon Sep 17 00:00:00 2001 From: Mohammad Samiul Islam Date: Mon, 10 Aug 2020 10:57:42 +0100 Subject: 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 --- .../android/server/pm/PackageInstallerSession.java | 158 ++++++++++++++------- .../java/com/android/server/pm/StagingManager.java | 10 +- 2 files changed, 114 insertions(+), 54 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 2aafe9a6f9a1..e6a62c5dce8c 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -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(); - 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()) { + // 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()) { + validateApexInstallLocked(); + } else { 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 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,8 +2214,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void onPackageInstalled(String basePackageName, int returnCode, String msg, Bundle extras) { - destroyInternal(); - dispatchSessionFinished(returnCode, msg, 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); + } } }; @@ -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() diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 35c26d6cd54e..529f16af883d 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -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); -- cgit v1.2.3-59-g8ed1b From 2de9c48ee3292d61ad6eea22f92a4a666e669185 Mon Sep 17 00:00:00 2001 From: Mohammad Samiul Islam Date: Mon, 14 Sep 2020 17:45:57 +0100 Subject: Ensure staged sessions are not installed in app-staging directory Since staged session files are stored in /data/app-staging folder, the existing logic will install the apk in /data/app-staging folder instead of /data/app. We need to change the logic so that the apk is moved from /data/app-staging folder to /data/app folder. Bug: 163037460 Test: atest StagedInstallTest Change-Id: I51027c138cce6952cd01c7f257a572135f0c3890 --- .../com/android/server/pm/PackageInstallerSession.java | 5 +++++ .../java/com/android/server/pm/PackageManagerService.java | 14 +++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index e6a62c5dce8c..7efe0810946a 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -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_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; @@ -2232,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); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index cc1ef86bfb1a..6da19df6ff67 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -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; @@ -15977,7 +15978,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 +16021,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(); -- cgit v1.2.3-59-g8ed1b From 038fd8d035689910230fa4bf32618e726befa13c Mon Sep 17 00:00:00 2001 From: Mohammad Samiul Islam Date: Tue, 15 Sep 2020 15:36:37 +0100 Subject: Reverify certain conditions during staged install There is a delay between when a staged session is verified and when it gets installed. The device state can mutate within this delay. As such we need to reverify certain conditions during install phase. For example, suppose we have a staged session for package X_v1 and it passes pre-reboot verification. But before we reboot, if install X_v2 using non-staged install flow. On reboot, when installing X_v1 we need to check for downgrade once again. Bug: 163037460 Test: atest StagedInstallTest#testInstallMultipleStagedSession_PartialFail_ApkOnly Change-Id: Ib9dd6eb6787ba8875644fa51919bc9010600509a --- .../android/server/pm/PackageManagerService.java | 118 ++++++++++++--------- 1 file changed, 68 insertions(+), 50 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6da19df6ff67..8f1576516e07 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -14991,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, @@ -15011,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, @@ -15033,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 @@ -15179,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); } @@ -15325,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(); @@ -15337,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++; @@ -24209,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. -- cgit v1.2.3-59-g8ed1b