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
This commit is contained in:
Mohammad Samiul Islam
2020-10-06 09:15:39 +00:00
committed by Android (Google) Code Review
3 changed files with 202 additions and 107 deletions

View File

@@ -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();
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 +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,8 +2215,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);
}
}
};
@@ -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,31 +3647,14 @@ 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;
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
// calling into the session
final SomeArgs args = SomeArgs.obtain();
args.arg1 = packageName;
args.arg2 = msg;
args.arg3 = extras;
args.arg4 = statusReceiver;
args.argi1 = returnCode;
mHandler.obtainMessage(MSG_ON_PACKAGE_INSTALLED, args).sendToTarget();
}
final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
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.
@@ -3625,6 +3669,27 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
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
// calling into the session
final SomeArgs args = SomeArgs.obtain();
args.arg1 = packageName;
args.arg2 = msg;
args.arg3 = extras;
args.arg4 = statusReceiver;
args.argi1 = returnCode;
mHandler.obtainMessage(MSG_ON_PACKAGE_INSTALLED, args).sendToTarget();
}
}
/** {@hide} */
void setStagedSessionReady() {
synchronized (mLock) {
@@ -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()

View File

@@ -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.

View File

@@ -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);