diff options
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 4b883cd4c17b..082ede2641ea 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<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> diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 12b366e3dbaa..25c73ee3f603 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1454,6 +1454,16 @@ public abstract class PackageManager { 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, diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 5e62b56c7bcd..42be07b3e3ba 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 6de7f07b78bb..dd96a2b84a97 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<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, diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 34648740d54c..6fc14e814525 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 35862db3d3db..30a23bfd0641 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 f55d1eb21d87..adee143691b1 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 a9115371413c..178719f3c4ec 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 a096f330689c..2a7ebfb75dde 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 5b0e2f3800c3..3a73dd9eb101 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 |