Merge changes from topics "cherrypick-non-staged-flag-ussa3zkzzhp", "forced-non-staged-apex-update-udc-dev-plus-aosp" into udc-dev-plus-aosp

* changes:
  Add an install flag to force non-staged APEX update
  Add --non-staged flag
This commit is contained in:
Nikita Ioffe
2023-07-29 00:34:37 +00:00
committed by Android (Google) Code Review
10 changed files with 99 additions and 28 deletions

View File

@@ -2369,6 +2369,8 @@ public class PackageInstaller {
public int requireUserAction = USER_ACTION_UNSPECIFIED;
/** {@hide} */
public boolean applicationEnabledSettingPersistent = false;
/** {@hide} */
public int developmentInstallFlags = 0;
private final ArrayMap<String, Integer> mPermissionStates;
@@ -2418,6 +2420,7 @@ public class PackageInstaller {
requireUserAction = source.readInt();
packageSource = source.readInt();
applicationEnabledSettingPersistent = source.readBoolean();
developmentInstallFlags = source.readInt();
}
/** {@hide} */
@@ -2449,6 +2452,7 @@ public class PackageInstaller {
ret.requireUserAction = requireUserAction;
ret.packageSource = packageSource;
ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
ret.developmentInstallFlags = developmentInstallFlags;
return ret;
}
@@ -3113,6 +3117,7 @@ public class PackageInstaller {
pw.printPair("rollbackDataPolicy", rollbackDataPolicy);
pw.printPair("applicationEnabledSettingPersistent",
applicationEnabledSettingPersistent);
pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
pw.println();
}
@@ -3154,6 +3159,7 @@ public class PackageInstaller {
dest.writeInt(requireUserAction);
dest.writeInt(packageSource);
dest.writeBoolean(applicationEnabledSettingPersistent);
dest.writeInt(developmentInstallFlags);
}
public static final Parcelable.Creator<SessionParams>

View File

@@ -1453,6 +1453,16 @@ public abstract class PackageManager {
@Retention(RetentionPolicy.SOURCE)
public @interface InstallFlags {}
/**
* Install flags that can only be used in development workflows (e.g. {@code adb install}).
* @hide
*/
@IntDef(flag = true, prefix = { "INSTALL_DEVELOPMENT_" }, value = {
INSTALL_DEVELOPMENT_FORCE_NON_STAGED_APEX_UPDATE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DevelopmentInstallFlags {}
/**
* Flag parameter for {@link #installPackage} to indicate that you want to
* replace an already installed package, if one exists.
@@ -1663,6 +1673,14 @@ public abstract class PackageManager {
*/
public static final int INSTALL_FROM_MANAGED_USER_OR_PROFILE = 1 << 26;
/**
* Flag parameter for {@link #installPackage} to force a non-staged update of an APEX. This is
* a development-only feature and should not be used on end user devices.
*
* @hide
*/
public static final int INSTALL_DEVELOPMENT_FORCE_NON_STAGED_APEX_UPDATE = 1;
/** @hide */
@IntDef(flag = true, value = {
DONT_KILL_APP,

View File

@@ -363,9 +363,13 @@ public abstract class ApexManager {
/**
* Performs a non-staged install of the given {@code apexFile}.
*
* If {@code force} is {@code true}, then update is forced even for APEXes that do not support
* non-staged update. This feature is only available on debuggable builds to improve development
* velocity of the teams that have their code packaged in an APEX.
*
* @return {@code ApeInfo} about the newly installed APEX package.
*/
abstract ApexInfo installPackage(File apexFile) throws PackageManagerException;
abstract ApexInfo installPackage(File apexFile, boolean force) throws PackageManagerException;
/**
* Get a list of apex system services implemented in an apex.
@@ -910,10 +914,11 @@ public abstract class ApexManager {
}
@Override
ApexInfo installPackage(File apexFile)
ApexInfo installPackage(File apexFile, boolean force)
throws PackageManagerException {
try {
return waitForApexService().installAndActivatePackage(apexFile.getAbsolutePath());
return waitForApexService().installAndActivatePackage(apexFile.getAbsolutePath(),
force);
} catch (RemoteException e) {
throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
"apexservice not available");
@@ -1170,7 +1175,7 @@ public abstract class ApexManager {
}
@Override
ApexInfo installPackage(File apexFile) {
ApexInfo installPackage(File apexFile, boolean force) {
throw new UnsupportedOperationException("APEX updates are not supported");
}

View File

@@ -43,6 +43,7 @@ final class InstallArgs {
final IPackageInstallObserver2 mObserver;
// Always refers to PackageManager flags only
final int mInstallFlags;
final int mDevelopmentInstallFlags;
@NonNull
final InstallSource mInstallSource;
final String mVolumeUuid;
@@ -69,8 +70,8 @@ final class InstallArgs {
@Nullable String[] mInstructionSets;
InstallArgs(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
int installFlags, InstallSource installSource, String volumeUuid,
UserHandle user, String[] instructionSets, String abiOverride,
int installFlags, int developmentInstallFlags, InstallSource installSource,
String volumeUuid, UserHandle user, String[] instructionSets, String abiOverride,
@NonNull ArrayMap<String, Integer> permissionStates,
List<String> allowlistedRestrictedPermissions,
int autoRevokePermissionsMode, String traceMethod, int traceCookie,
@@ -80,6 +81,7 @@ final class InstallArgs {
mOriginInfo = originInfo;
mMoveInfo = moveInfo;
mInstallFlags = installFlags;
mDevelopmentInstallFlags = developmentInstallFlags;
mObserver = observer;
mInstallSource = Preconditions.checkNotNull(installSource);
mVolumeUuid = volumeUuid;
@@ -105,7 +107,7 @@ final class InstallArgs {
* when cleaning up old installs, or used as a move source.
*/
InstallArgs(String codePath, String[] instructionSets) {
this(OriginInfo.fromNothing(), null, null, 0, InstallSource.EMPTY, null, null,
this(OriginInfo.fromNothing(), null, null, 0, 0, InstallSource.EMPTY, null, null,
instructionSets, null, new ArrayMap<>(), null, MODE_DEFAULT, null, 0,
SigningDetails.UNKNOWN, PackageManager.INSTALL_REASON_UNKNOWN,
PackageManager.INSTALL_SCENARIO_DEFAULT, false, DataLoaderType.NONE,

View File

@@ -134,12 +134,13 @@ final class InstallRequest {
InstallRequest(InstallingSession params) {
mUserId = params.getUser().getIdentifier();
mInstallArgs = new InstallArgs(params.mOriginInfo, params.mMoveInfo, params.mObserver,
params.mInstallFlags, params.mInstallSource, params.mVolumeUuid,
params.getUser(), null /*instructionSets*/, params.mPackageAbiOverride,
params.mPermissionStates, params.mAllowlistedRestrictedPermissions,
params.mAutoRevokePermissionsMode, params.mTraceMethod, params.mTraceCookie,
params.mSigningDetails, params.mInstallReason, params.mInstallScenario,
params.mForceQueryableOverride, params.mDataLoaderType, params.mPackageSource,
params.mInstallFlags, params.mDevelopmentInstallFlags, params.mInstallSource,
params.mVolumeUuid, params.getUser(), null /*instructionSets*/,
params.mPackageAbiOverride, params.mPermissionStates,
params.mAllowlistedRestrictedPermissions, params.mAutoRevokePermissionsMode,
params.mTraceMethod, params.mTraceCookie, params.mSigningDetails,
params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
params.mDataLoaderType, params.mPackageSource,
params.mApplicationEnabledSettingPersistent);
mPackageMetrics = new PackageMetrics(this);
mIsInstallInherit = params.mIsInherit;
@@ -286,6 +287,10 @@ final class InstallRequest {
return mInstallArgs == null ? 0 : mInstallArgs.mInstallFlags;
}
public int getDevelopmentInstallFlags() {
return mInstallArgs == null ? 0 : mInstallArgs.mDevelopmentInstallFlags;
}
public int getInstallReason() {
return mInstallArgs == null ? INSTALL_REASON_UNKNOWN : mInstallArgs.mInstallReason;
}

View File

@@ -68,6 +68,7 @@ class InstallingSession {
final MoveInfo mMoveInfo;
final IPackageInstallObserver2 mObserver;
int mInstallFlags;
int mDevelopmentInstallFlags;
@NonNull
final InstallSource mInstallSource;
final String mVolumeUuid;
@@ -102,8 +103,8 @@ class InstallingSession {
// For move install
InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
int installFlags, InstallSource installSource, String volumeUuid,
UserHandle user, String packageAbiOverride, int packageSource,
int installFlags, int developmentInstallFlags, InstallSource installSource,
String volumeUuid, UserHandle user, String packageAbiOverride, int packageSource,
PackageLite packageLite, PackageManagerService pm) {
mPm = pm;
mUser = user;
@@ -113,6 +114,7 @@ class InstallingSession {
mMoveInfo = moveInfo;
mObserver = observer;
mInstallFlags = installFlags;
mDevelopmentInstallFlags = developmentInstallFlags;
mInstallSource = Preconditions.checkNotNull(installSource);
mVolumeUuid = volumeUuid;
mPackageAbiOverride = packageAbiOverride;
@@ -149,6 +151,7 @@ class InstallingSession {
mInstallScenario = sessionParams.installScenario;
mObserver = observer;
mInstallFlags = sessionParams.installFlags;
mDevelopmentInstallFlags = sessionParams.developmentInstallFlags;
mInstallSource = installSource;
mVolumeUuid = sessionParams.volumeUuid;
mPackageAbiOverride = sessionParams.abiOverride;
@@ -592,6 +595,10 @@ class InstallingSession {
"Only a non-staged install of a single APEX is supported");
}
InstallRequest request = requests.get(0);
boolean force =
(request.getDevelopmentInstallFlags()
& PackageManager.INSTALL_DEVELOPMENT_FORCE_NON_STAGED_APEX_UPDATE)
!= 0;
try {
// Should directory scanning logic be moved to ApexManager for better test coverage?
final File dir = request.getOriginInfo().mResolvedFile;
@@ -608,7 +615,7 @@ class InstallingSession {
PackageManagerException.INTERNAL_ERROR_APEX_MORE_THAN_ONE_FILE);
}
try (PackageParser2 packageParser = mPm.mInjector.getScanningPackageParser()) {
ApexInfo apexInfo = mPm.mApexManager.installPackage(apexes[0]);
ApexInfo apexInfo = mPm.mApexManager.installPackage(apexes[0], force);
// APEX has been handled successfully by apexd. Let's continue the install flow
// so it will be scanned and registered with the system.
// TODO(b/225756739): Improve atomicity of rebootless APEX install.

View File

@@ -305,8 +305,9 @@ public final class MovePackageHelper {
new File(origin.mResolvedPath), /* flags */ 0);
final PackageLite lite = ret.isSuccess() ? ret.getResult() : null;
final InstallingSession installingSession = new InstallingSession(origin, move,
installObserver, installFlags, installSource, volumeUuid, user, packageAbiOverride,
PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, lite, mPm);
installObserver, installFlags, /* developmentInstallFlags= */ 0, installSource,
volumeUuid, user, packageAbiOverride, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED,
lite, mPm);
installingSession.movePackage();
}

View File

@@ -709,6 +709,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
!= PackageManager.PERMISSION_GRANTED) {
params.installFlags &= ~PackageManager.INSTALL_ALLOW_TEST;
}
// developmentInstallFlags can ony be set by shell or root.
params.developmentInstallFlags = 0;
}
String originatingPackageName = null;

View File

@@ -3211,6 +3211,13 @@ class PackageManagerShellCommand extends ShellCommand {
// Set package source to other by default
sessionParams.setPackageSource(PackageInstaller.PACKAGE_SOURCE_OTHER);
// Encodes one of the states:
// 1. Install request explicitly specified --staged, then value will be true.
// 2. Install request explicitly specified --non-staged, then value will be false.
// 3. Install request did not specify either --staged or --non-staged, then for APEX
// installs the value will be true, and for apk installs it will be false.
Boolean staged = null;
String opt;
boolean replaceExisting = true;
boolean forceNonStaged = false;
@@ -3319,7 +3326,6 @@ class PackageManagerShellCommand extends ShellCommand {
break;
case "--apex":
sessionParams.setInstallAsApex();
sessionParams.setStaged();
break;
case "--force-non-staged":
forceNonStaged = true;
@@ -3328,7 +3334,10 @@ class PackageManagerShellCommand extends ShellCommand {
sessionParams.setMultiPackage();
break;
case "--staged":
sessionParams.setStaged();
staged = true;
break;
case "--non-staged":
staged = false;
break;
case "--force-queryable":
sessionParams.setForceQueryable();
@@ -3363,11 +3372,18 @@ class PackageManagerShellCommand extends ShellCommand {
throw new IllegalArgumentException("Unknown option " + opt);
}
}
if (staged == null) {
staged = (sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0;
}
if (replaceExisting) {
sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
}
if (forceNonStaged) {
sessionParams.isStaged = false;
sessionParams.developmentInstallFlags |=
PackageManager.INSTALL_DEVELOPMENT_FORCE_NON_STAGED_APEX_UPDATE;
} else if (staged) {
sessionParams.setStaged();
}
return params;
}
@@ -4231,7 +4247,8 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" [--preload] [--instant] [--full] [--dont-kill]");
pw.println(" [--enable-rollback]");
pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
pw.println(" [--apex] [--force-non-staged] [--staged-ready-timeout TIMEOUT]");
pw.println(" [--apex] [--non-staged] [--force-non-staged]");
pw.println(" [--staged-ready-timeout TIMEOUT]");
pw.println(" [PATH [SPLIT...]|-]");
pw.println(" Install an application. Must provide the apk data to install, either as");
pw.println(" file path(s) or '-' to read from stdin. Options are:");
@@ -4260,8 +4277,12 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" --update-ownership: request the update ownership enforcement");
pw.println(" --force-uuid: force install on to disk volume with given UUID");
pw.println(" --apex: install an .apex file, not an .apk");
pw.println(" --non-staged: explicitly set this installation to be non-staged.");
pw.println(" This flag is only useful for APEX installs that are implicitly");
pw.println(" assumed to be staged.");
pw.println(" --force-non-staged: force the installation to run under a non-staged");
pw.println(" session, which may complete without requiring a reboot");
pw.println(" session, which may complete without requiring a reboot. This will");
pw.println(" force a rebootless update even for APEXes that don't support it");
pw.println(" --staged-ready-timeout: By default, staged sessions wait "
+ DEFAULT_STAGED_READY_TIMEOUT_MS);
pw.println(" milliseconds for pre-reboot verification to complete when");

View File

@@ -19,6 +19,7 @@ package com.android.server.pm;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
@@ -368,10 +369,11 @@ public class ApexManagerTest {
File finalApex = extractResource("test.rebootles_apex_v2", "test.rebootless_apex_v2.apex");
ApexInfo newApexInfo = createApexInfo("test.apex_rebootless", 2, /* isActive= */ true,
/* isFactory= */ false, finalApex);
when(mApexService.installAndActivatePackage(anyString())).thenReturn(newApexInfo);
when(mApexService.installAndActivatePackage(anyString(), anyBoolean())).thenReturn(
newApexInfo);
File installedApex = extractResource("installed", "test.rebootless_apex_v2.apex");
newApexInfo = mApexManager.installPackage(installedApex);
newApexInfo = mApexManager.installPackage(installedApex, /* force= */ false);
var newPkg = mockParsePackage(mPackageParser2, newApexInfo);
assertThat(newPkg.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath());
@@ -398,10 +400,11 @@ public class ApexManagerTest {
File finalApex = extractResource("test.rebootles_apex_v2", "test.rebootless_apex_v2.apex");
ApexInfo newApexInfo = createApexInfo("test.apex_rebootless", 2, /* isActive= */ true,
/* isFactory= */ false, finalApex);
when(mApexService.installAndActivatePackage(anyString())).thenReturn(newApexInfo);
when(mApexService.installAndActivatePackage(anyString(), anyBoolean())).thenReturn(
newApexInfo);
File installedApex = extractResource("installed", "test.rebootless_apex_v2.apex");
newApexInfo = mApexManager.installPackage(installedApex);
newApexInfo = mApexManager.installPackage(installedApex, /* force= */ false);
var newPkg = mockParsePackage(mPackageParser2, newApexInfo);
assertThat(newPkg.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath());
@@ -416,13 +419,13 @@ public class ApexManagerTest {
@Test
public void testInstallPackageBinderCallFails() throws Exception {
when(mApexService.installAndActivatePackage(anyString())).thenThrow(
when(mApexService.installAndActivatePackage(anyString(), anyBoolean())).thenThrow(
new RuntimeException("install failed :("));
File installedApex = extractResource("test.apex_rebootless_v1",
"test.rebootless_apex_v1.apex");
assertThrows(PackageManagerException.class,
() -> mApexManager.installPackage(installedApex));
() -> mApexManager.installPackage(installedApex, /* force= */ false));
}
@Test