From cf573cdedee2e5e3e07d4c17c872c98c32a47636 Mon Sep 17 00:00:00 2001 From: Nikita Ioffe Date: Thu, 20 Jul 2023 00:05:49 +0100 Subject: [PATCH 1/2] Add --non-staged flag The child change will change the behaviour of --force-non-staged flag to perform a rebootless APEX update even for APEXes that don't support rebootless updates. This behaviour will be used to speed up development cycle for teams that have their code packaged in an APEX. However, some developers (and some tests) assume that adb install --force-non-staged will fail for APEXes that don't support rebootless updates. In order to preserve such behaviour, this change introduces a new --non-staged flag. Bug: 290750901 Test: adb install --non-staged apex-supporting-rebootless-update Test: verify install was successful Test: adb install --non-staged com.android.virt.apex Test: verify install failed with "does not support non-staged update" Merged-In: I27ac000207e1b6ec39890bd382b3751fbb62e265 Change-Id: I27ac000207e1b6ec39890bd382b3751fbb62e265 --- .../server/pm/PackageManagerShellCommand.java | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index e043c90f4f140..a096f330689cc 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -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,16 @@ 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; + } else if (staged) { + sessionParams.setStaged(); } return params; } @@ -4231,7 +4245,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,6 +4275,9 @@ 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(" --staged-ready-timeout: By default, staged sessions wait " From b45adeaf1527d2e15cd0cbb8930f2d36ccf65ba6 Mon Sep 17 00:00:00 2001 From: Nikita Ioffe Date: Mon, 17 Jul 2023 22:21:27 +0100 Subject: [PATCH 2/2] 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 914e4761bef4f12cfee8ae5c03fe5798174cccde) --- .../android/content/pm/PackageInstaller.java | 6 ++++++ .../android/content/pm/PackageManager.java | 18 ++++++++++++++++++ .../com/android/server/pm/ApexManager.java | 13 +++++++++---- .../com/android/server/pm/InstallArgs.java | 8 +++++--- .../com/android/server/pm/InstallRequest.java | 17 +++++++++++------ .../android/server/pm/InstallingSession.java | 13 ++++++++++--- .../android/server/pm/MovePackageHelper.java | 5 +++-- .../server/pm/PackageInstallerService.java | 3 +++ .../server/pm/PackageManagerShellCommand.java | 5 ++++- .../com/android/server/pm/ApexManagerTest.java | 15 +++++++++------ 10 files changed, 78 insertions(+), 25 deletions(-) diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 4b883cd4c17bc..082ede2641ea1 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -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 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 diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 4529f897620e1..0aba6f7ab870b 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -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, diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 5e62b56c7bcd4..42be07b3e3ba2 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -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"); } diff --git a/services/core/java/com/android/server/pm/InstallArgs.java b/services/core/java/com/android/server/pm/InstallArgs.java index 6de7f07b78bb1..dd96a2b84a978 100644 --- a/services/core/java/com/android/server/pm/InstallArgs.java +++ b/services/core/java/com/android/server/pm/InstallArgs.java @@ -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 permissionStates, List 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, diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 34648740d54c5..6fc14e8145250 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -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; } diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java index 35862db3d3db2..30a23bfd06413 100644 --- a/services/core/java/com/android/server/pm/InstallingSession.java +++ b/services/core/java/com/android/server/pm/InstallingSession.java @@ -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. diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java index f55d1eb21d873..adee143691b17 100644 --- a/services/core/java/com/android/server/pm/MovePackageHelper.java +++ b/services/core/java/com/android/server/pm/MovePackageHelper.java @@ -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(); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index a9115371413cb..178719f3c4ecb 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -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; diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index a096f330689cc..2a7ebfb75dde7 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -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"); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java index 5b0e2f3800c32..3a73dd9eb101f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java @@ -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