From f85f3a299b200cbf748371bfc99f7e0968a32fe8 Mon Sep 17 00:00:00 2001 From: Nikita Ioffe Date: Thu, 20 Jul 2023 00:05:49 +0100 Subject: 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 (cherry picked from commit d2d672e3a409d9b924f9c03c036763360bb8e06f) --- .../server/pm/PackageManagerShellCommand.java | 25 ++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index be842b96843c..9080d4622cf4 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,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; } @@ -3978,7 +3991,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 +4020,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 " -- cgit v1.2.3-59-g8ed1b From 5770e468a7d6f35363e620e17d5df3dd10665580 Mon Sep 17 00:00:00 2001 From: Nikita Ioffe Date: Mon, 17 Jul 2023 14:15:02 +0100 Subject: 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: Ib452dcdbdd30991f60205f94be25389379153183 --- core/java/android/content/pm/PackageManager.java | 8 ++++++++ .../java/com/android/server/pm/ApexManager.java | 8 ++++---- .../android/server/pm/InstallPackageHelper.java | 4 +++- .../server/pm/PackageManagerShellCommand.java | 1 + .../src/com/android/server/pm/ApexManagerTest.java | 24 ++++++++++++++-------- 5 files changed, 31 insertions(+), 14 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 9080d4622cf4..38b79e5612ef 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -3208,6 +3208,7 @@ class PackageManagerShellCommand extends ShellCommand { } if (forceNonStaged) { sessionParams.isStaged = false; + sessionParams.installFlags |= PackageManager.INSTALL_FORCE_NON_STAGED_APEX_UPDATE; } else if (staged) { sessionParams.setStaged(); } 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 "); } -- cgit v1.2.3-59-g8ed1b