diff options
5 files changed, 52 insertions, 18 deletions
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index d738d9eec4cb..db4a684b667e 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1561,6 +1561,14 @@ public abstract class PackageManager { */ public static final int INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK = 0x01000000; + /** + * 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_FORCE_NON_STAGED_APEX_UPDATE = 0x02000000; + /** @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 1a6155b43f6b..a085b95d8e77 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -424,7 +424,7 @@ public abstract class ApexManager { /** * Performs a non-staged install of the given {@code apexFile}. */ - abstract void installPackage(File apexFile, PackageParser2 packageParser) + abstract void installPackage(File apexFile, PackageParser2 packageParser, boolean force) throws PackageManagerException; /** @@ -1136,7 +1136,7 @@ public abstract class ApexManager { } @Override - void installPackage(File apexFile, PackageParser2 packageParser) + void installPackage(File apexFile, PackageParser2 packageParser, boolean force) throws PackageManagerException { try { final int flags = PackageManager.GET_META_DATA @@ -1159,7 +1159,7 @@ public abstract class ApexManager { } checkApexSignature(existingApexPkg, newApexPkg); ApexInfo apexInfo = waitForApexService().installAndActivatePackage( - apexFile.getAbsolutePath()); + apexFile.getAbsolutePath(), force); final ParsedPackage parsedPackage2 = packageParser.parsePackage( new File(apexInfo.modulePath), flags, /* useCaches= */ false); final PackageInfo finalApexPkg = PackageInfoWithoutStateUtils.generate( @@ -1505,7 +1505,7 @@ public abstract class ApexManager { } @Override - void installPackage(File apexFile, PackageParser2 packageParser) { + void installPackage(File apexFile, PackageParser2 packageParser, boolean force) { throw new UnsupportedOperationException("APEX updates are not supported"); } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 293f7e9941e3..703ae11ae99b 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -872,8 +872,10 @@ final class InstallPackageHelper { "Expected exactly one .apex file under " + dir.getAbsolutePath() + " got: " + apexes.length); } + boolean force = (request.mArgs.mInstallFlags + & PackageManager.INSTALL_FORCE_NON_STAGED_APEX_UPDATE) != 0; try (PackageParser2 packageParser = mPm.mInjector.getScanningPackageParser()) { - mApexManager.installPackage(apexes[0], packageParser); + mApexManager.installPackage(apexes[0], packageParser, force); } } catch (PackageManagerException e) { request.mInstallResult.setError("APEX installation failed", e); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index be842b96843c..38b79e5612ef 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -86,7 +86,6 @@ import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.os.ShellCommand; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; @@ -3053,6 +3052,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; @@ -3151,7 +3157,6 @@ class PackageManagerShellCommand extends ShellCommand { break; case "--apex": sessionParams.setInstallAsApex(); - sessionParams.setStaged(); break; case "--force-non-staged": forceNonStaged = true; @@ -3160,7 +3165,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(); @@ -3192,11 +3200,17 @@ 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.installFlags |= PackageManager.INSTALL_FORCE_NON_STAGED_APEX_UPDATE; + } else if (staged) { + sessionParams.setStaged(); } return params; } @@ -3978,7 +3992,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:"); @@ -4006,6 +4021,9 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" 3=device setup, 4=user request"); 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 " diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java index ab292ab5381e..872e438c6b8d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java +++ b/services/tests/servicestests/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; @@ -362,7 +363,7 @@ public class ApexManagerTest { File apex = extractResource("test.apex_rebootless_v1", "test.rebootless_apex_v1.apex"); PackageManagerException e = expectThrows(PackageManagerException.class, - () -> mApexManager.installPackage(apex, mPackageParser2)); + () -> mApexManager.installPackage(apex, mPackageParser2, /* force= */ false)); assertThat(e).hasMessageThat().contains("It is forbidden to install new APEX packages"); } @@ -378,10 +379,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"); - mApexManager.installPackage(installedApex, mPackageParser2); + mApexManager.installPackage(installedApex, mPackageParser2, /* force= */ false); PackageInfo newInfo = mApexManager.getPackageInfo("test.apex.rebootless", ApexManager.MATCH_ACTIVE_PACKAGE); @@ -416,10 +418,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"); - mApexManager.installPackage(installedApex, mPackageParser2); + mApexManager.installPackage(installedApex, mPackageParser2, /* force= */ false); PackageInfo newInfo = mApexManager.getPackageInfo("test.apex.rebootless", ApexManager.MATCH_ACTIVE_PACKAGE); @@ -447,13 +450,14 @@ public class ApexManagerTest { mApexManager.scanApexPackagesTraced(mPackageParser2, ParallelPackageParser.makeExecutorService()); - 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, mPackageParser2)); + () -> mApexManager.installPackage(installedApex, mPackageParser2, /* force= */ + false)); } @Test @@ -468,7 +472,8 @@ public class ApexManagerTest { File installedApex = extractResource("shim_different_certificate", "com.android.apex.cts.shim.v2_different_certificate.apex"); PackageManagerException e = expectThrows(PackageManagerException.class, - () -> mApexManager.installPackage(installedApex, mPackageParser2)); + () -> mApexManager.installPackage(installedApex, mPackageParser2, /* force= */ + false)); assertThat(e).hasMessageThat().contains("APK container signature of "); assertThat(e).hasMessageThat().contains( "is not compatible with currently installed on device"); @@ -486,7 +491,8 @@ public class ApexManagerTest { File installedApex = extractResource("shim_unsigned_apk_container", "com.android.apex.cts.shim.v2_unsigned_apk_container.apex"); PackageManagerException e = expectThrows(PackageManagerException.class, - () -> mApexManager.installPackage(installedApex, mPackageParser2)); + () -> mApexManager.installPackage(installedApex, mPackageParser2, /* force= */ + false)); assertThat(e).hasMessageThat().contains("Failed to collect certificates from "); } |