Add an install flag to force non-staged APEX update
This is a development only feature to speed up development workflow for
teams that have their code package in an APEX.
Bug: 290750901
Test: m
Test: atest ApexManagerTest
Test: see other change in the topic
Merged-In: Ic0abb9e97d529910805af50208baf42b9b28b171
Change-Id: Ic0abb9e97d529910805af50208baf42b9b28b171
(cherry picked from commit 914e4761be)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -1435,6 +1435,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.
|
||||
@@ -1645,6 +1655,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,
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3380,6 +3380,8 @@ class PackageManagerShellCommand extends ShellCommand {
|
||||
}
|
||||
if (forceNonStaged) {
|
||||
sessionParams.isStaged = false;
|
||||
sessionParams.developmentInstallFlags |=
|
||||
PackageManager.INSTALL_DEVELOPMENT_FORCE_NON_STAGED_APEX_UPDATE;
|
||||
} else if (staged) {
|
||||
sessionParams.setStaged();
|
||||
}
|
||||
@@ -4279,7 +4281,8 @@ class PackageManagerShellCommand extends ShellCommand {
|
||||
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");
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user