diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index a8dcbaffeeb51..6cbace4c65ba4 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -241,7 +241,11 @@ public class SystemConfig { private final ArraySet mRollbackWhitelistedPackages = new ArraySet<>(); private final ArraySet mWhitelistedStagedInstallers = new ArraySet<>(); - private final ArraySet mAllowedVendorApexes = new ArraySet<>(); + // A map from package name of vendor APEXes that can be updated to an installer package name + // allowed to install updates for it. + private final ArrayMap mAllowedVendorApexes = new ArrayMap<>(); + + private String mModulesInstallerPackageName; /** * Map of system pre-defined, uniquely named actors; keys are namespace, @@ -412,10 +416,14 @@ public class SystemConfig { return mWhitelistedStagedInstallers; } - public Set getAllowedVendorApexes() { + public Map getAllowedVendorApexes() { return mAllowedVendorApexes; } + public String getModulesInstallerPackageName() { + return mModulesInstallerPackageName; + } + public ArraySet getAppDataIsolationWhitelistedApps() { return mAppDataIsolationWhitelistedApps; } @@ -1210,12 +1218,21 @@ public class SystemConfig { case "whitelisted-staged-installer": { if (allowAppConfigs) { String pkgname = parser.getAttributeValue(null, "package"); + boolean isModulesInstaller = XmlUtils.readBooleanAttribute( + parser, "isModulesInstaller", false); if (pkgname == null) { Slog.w(TAG, "<" + name + "> without package in " + permFile + " at " + parser.getPositionDescription()); } else { mWhitelistedStagedInstallers.add(pkgname); } + if (isModulesInstaller) { + if (mModulesInstallerPackageName != null) { + throw new IllegalStateException( + "Multiple modules installers"); + } + mModulesInstallerPackageName = pkgname; + } } else { logNotAllowedInPartition(name, permFile, parser); } @@ -1224,11 +1241,18 @@ public class SystemConfig { case "allowed-vendor-apex": { if (allowVendorApex) { String pkgName = parser.getAttributeValue(null, "package"); + String installerPkgName = parser.getAttributeValue( + null, "installerPackage"); if (pkgName == null) { Slog.w(TAG, "<" + name + "> without package in " + permFile + " at " + parser.getPositionDescription()); - } else { - mAllowedVendorApexes.add(pkgName); + } + if (installerPkgName == null) { + Slog.w(TAG, "<" + name + "> without installerPackage in " + permFile + + " at " + parser.getPositionDescription()); + } + if (pkgName != null && installerPkgName != null) { + mAllowedVendorApexes.put(pkgName, installerPkgName); } } else { logNotAllowedInPartition(name, permFile, parser); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 00ef97d7a17a6..542948491dc8c 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2252,12 +2252,29 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { (params.installFlags & PackageManager.INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK) == 0; synchronized (mLock) { - if (checkApexUpdateAllowed && !isApexUpdateAllowed(mPackageName)) { + if (checkApexUpdateAllowed && !isApexUpdateAllowed(mPackageName, + mInstallSource.installerPackageName)) { onSessionValidationFailure(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, - "Update of APEX package " + mPackageName + " is not allowed"); + "Update of APEX package " + mPackageName + " is not allowed for " + + mInstallSource.installerPackageName); return; } } + + if (!params.isStaged) { + // For non-staged APEX installs also check if there is a staged session that + // contains the same APEX. If that's the case, we should fail this session. + synchronized (mLock) { + int sessionId = mStagingManager.getSessionIdByPackageName(mPackageName); + if (sessionId != -1) { + onSessionValidationFailure( + PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, + "Staged session " + sessionId + " already contains " + + mPackageName); + return; + } + } + } } if (params.isStaged) { @@ -2798,9 +2815,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return sessionContains((s) -> !s.isApexSession()); } - private boolean isApexUpdateAllowed(String apexPackageName) { - return mPm.getModuleInfo(apexPackageName, 0) != null - || SystemConfig.getInstance().getAllowedVendorApexes().contains(apexPackageName); + private boolean isApexUpdateAllowed(String apexPackageName, String installerPackageName) { + if (mPm.getModuleInfo(apexPackageName, 0) != null) { + final String modulesInstaller = + SystemConfig.getInstance().getModulesInstallerPackageName(); + if (modulesInstaller == null) { + Slog.w(TAG, "No modules installer defined"); + return false; + } + return modulesInstaller.equals(installerPackageName); + } + final String vendorApexInstaller = + SystemConfig.getInstance().getAllowedVendorApexes().get(apexPackageName); + if (vendorApexInstaller == null) { + Slog.w(TAG, apexPackageName + " is not allowed to be updated"); + return false; + } + return vendorApexInstaller.equals(installerPackageName); } /** diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 4ac5be2ec7c7c..bdbcb277e8d66 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -777,6 +777,26 @@ public class StagingManager { } } + /** + * Returns id of a committed and non-finalized stated session that contains same + * {@code packageName}, or {@code -1} if no sessions have this {@code packageName} staged. + */ + int getSessionIdByPackageName(@NonNull String packageName) { + synchronized (mStagedSessions) { + for (int i = 0; i < mStagedSessions.size(); i++) { + StagedSession stagedSession = mStagedSessions.valueAt(i); + if (!stagedSession.isCommitted() || stagedSession.isDestroyed() + || stagedSession.isInTerminalState()) { + continue; + } + if (stagedSession.getPackageName().equals(packageName)) { + return stagedSession.sessionId(); + } + } + } + return -1; + } + @VisibleForTesting void createSession(@NonNull StagedSession sessionInfo) { synchronized (mStagedSessions) { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java index f91cb2801bc12..521be70df633f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java @@ -459,6 +459,66 @@ public class StagingManagerTest { assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); } + @Test + public void getSessionIdByPackageName() throws Exception { + FakeStagedSession session = new FakeStagedSession(239); + session.setCommitted(true); + session.setSessionReady(); + session.setPackageName("com.foo"); + + mStagingManager.createSession(session); + assertThat(mStagingManager.getSessionIdByPackageName("com.foo")).isEqualTo(239); + } + + @Test + public void getSessionIdByPackageName_appliedSession_ignores() throws Exception { + FakeStagedSession session = new FakeStagedSession(37); + session.setCommitted(true); + session.setSessionApplied(); + session.setPackageName("com.foo"); + + mStagingManager.createSession(session); + assertThat(mStagingManager.getSessionIdByPackageName("com.foo")).isEqualTo(-1); + } + + @Test + public void getSessionIdByPackageName_failedSession_ignores() throws Exception { + FakeStagedSession session = new FakeStagedSession(73); + session.setCommitted(true); + session.setSessionFailed(1, "whatevs"); + session.setPackageName("com.foo"); + + mStagingManager.createSession(session); + assertThat(mStagingManager.getSessionIdByPackageName("com.foo")).isEqualTo(-1); + } + + @Test + public void getSessionIdByPackageName_destroyedSession_ignores() throws Exception { + FakeStagedSession session = new FakeStagedSession(23); + session.setCommitted(true); + session.setDestroyed(true); + session.setPackageName("com.foo"); + + mStagingManager.createSession(session); + assertThat(mStagingManager.getSessionIdByPackageName("com.foo")).isEqualTo(-1); + } + + @Test + public void getSessionIdByPackageName_noSessions() throws Exception { + assertThat(mStagingManager.getSessionIdByPackageName("com.foo")).isEqualTo(-1); + } + + @Test + public void getSessionIdByPackageName_noSessionHasThisPackage() throws Exception { + FakeStagedSession session = new FakeStagedSession(37); + session.setCommitted(true); + session.setSessionApplied(); + session.setPackageName("com.foo"); + + mStagingManager.createSession(session); + assertThat(mStagingManager.getSessionIdByPackageName("com.bar")).isEqualTo(-1); + } + private StagingManager.StagedSession createSession(int sessionId, String packageName, long committedMillis) { PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java index 9044b27d49944..5eb21a58c38e8 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -19,6 +19,7 @@ package com.android.server.systemconfig; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.testng.Assert.expectThrows; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; @@ -200,6 +201,46 @@ public class SystemConfigTest { assertThat(mSysConfig.getWhitelistedStagedInstallers()) .containsExactly("com.android.package1"); + assertThat(mSysConfig.getModulesInstallerPackageName()).isNull(); + } + + @Test + public void readPermissions_parsesStagedInstallerWhitelist_modulesInstaller() + throws IOException { + final String contents = + "\n" + + " \n" + + ""; + final File folder = createTempSubfolder("folder"); + createTempFile(folder, "staged-installer-whitelist.xml", contents); + + mSysConfig.readPermissions(folder, /* Grant all permission flags */ ~0); + + assertThat(mSysConfig.getWhitelistedStagedInstallers()) + .containsExactly("com.android.package1"); + assertThat(mSysConfig.getModulesInstallerPackageName()) + .isEqualTo("com.android.package1"); + } + + @Test + public void readPermissions_parsesStagedInstallerWhitelist_multipleModulesInstallers() + throws IOException { + final String contents = + "\n" + + " \n" + + " \n" + + ""; + final File folder = createTempSubfolder("folder"); + createTempFile(folder, "staged-installer-whitelist.xml", contents); + + IllegalStateException e = expectThrows( + IllegalStateException.class, + () -> mSysConfig.readPermissions(folder, /* Grant all permission flags */ ~0)); + + assertThat(e).hasMessageThat().contains("Multiple modules installers"); } /** @@ -230,14 +271,16 @@ public class SystemConfigTest { throws IOException { final String contents = "\n" - + " \n" + + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "vendor-apex-allowlist.xml", contents); mSysConfig.readPermissions(folder, /* Grant all permission flags */ ~0); - assertThat(mSysConfig.getAllowedVendorApexes()).containsExactly("com.android.apex1"); + assertThat(mSysConfig.getAllowedVendorApexes()) + .containsExactly("com.android.apex1", "com.installer"); } /** diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp index 1aa04996f6827..cac14a72a7061 100644 --- a/tests/StagedInstallTest/Android.bp +++ b/tests/StagedInstallTest/Android.bp @@ -32,6 +32,7 @@ android_test_helper_app { test_suites: ["general-tests"], java_resources: [ ":com.android.apex.apkrollback.test_v2", + ":StagedInstallTestApexV2", ":StagedInstallTestApexV2_WrongSha", ":test.rebootless_apex_v1", ":test.rebootless_apex_v2", diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java index 738e68e336741..4684f0182d034 100644 --- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -55,8 +55,11 @@ public class StagedInstallInternalTest { private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2", APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex"); private static final TestApp APEX_WRONG_SHA_V2 = new TestApp( - "ApexWrongSha2", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true, + "ApexWrongSha2", SHIM_APEX_PACKAGE_NAME, 2, /* isApex= */ true, "com.android.apex.cts.shim.v2_wrong_sha.apex"); + private static final TestApp APEX_V2 = new TestApp( + "ApexV2", SHIM_APEX_PACKAGE_NAME, 2, /* isApex= */ true, + "com.android.apex.cts.shim.v2.apex"); private File mTestStateFile = new File( InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(), @@ -236,6 +239,96 @@ public class StagedInstallInternalTest { assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); } + @Test + public void testApexInstallerNotInAllowListCanNotInstall_staged() throws Exception { + assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); + // We don't really care which APEX we are trying to install here, since the session creation + // should fail immediately. + InstallUtils.commitExpectingFailure( + SecurityException.class, + "Installer not allowed to commit staged install", + Install.single(APEX_WRONG_SHA_V2).setBypassStangedInstallerCheck(false) + .setStaged()); + } + + @Test + public void testApexInstallerNotInAllowListCanNotInstall_nonStaged() throws Exception { + assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); + // We don't really care which APEX we are trying to install here, since the session creation + // should fail immediately. + InstallUtils.commitExpectingFailure( + SecurityException.class, + "Installer not allowed to commit non-staged APEX install", + Install.single(APEX_WRONG_SHA_V2).setBypassStangedInstallerCheck(false)); + } + + @Test + public void testApexNotInAllowListCanNotInstall_staged() throws Exception { + assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); + TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + InstallUtils.commitExpectingFailure( + AssertionError.class, + "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal", + Install.single(apex).setBypassAllowedApexUpdateCheck(false).setStaged()); + } + + @Test + public void testApexNotInAllowListCanNotInstall_nonStaged() throws Exception { + assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); + TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + InstallUtils.commitExpectingFailure( + AssertionError.class, + "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal", + Install.single(apex).setBypassAllowedApexUpdateCheck(false)); + } + + @Test + public void testVendorApexWrongInstaller_staged() throws Exception { + assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); + TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + InstallUtils.commitExpectingFailure( + AssertionError.class, + "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal", + Install.single(apex).setBypassAllowedApexUpdateCheck(false).setStaged()); + } + + @Test + public void testVendorApexWrongInstaller_nonStaged() throws Exception { + assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); + TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + InstallUtils.commitExpectingFailure( + AssertionError.class, + "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal", + Install.single(apex).setBypassAllowedApexUpdateCheck(false)); + } + + @Test + public void testVendorApexCorrectInstaller_staged() throws Exception { + assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); + TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + int sessionId = + Install.single(apex).setBypassAllowedApexUpdateCheck(false).setStaged().commit(); + InstallUtils.getPackageInstaller().abandonSession(sessionId); + } + + @Test + public void testVendorApexCorrectInstaller_nonStaged() throws Exception { + assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); + TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + Install.single(apex).setBypassAllowedApexUpdateCheck(false).commit(); + assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(2); + } + @Test public void testRebootlessUpdates() throws Exception { InstallUtils.dropShellPermissionIdentity(); @@ -298,6 +391,19 @@ public class StagedInstallInternalTest { } } + @Test + public void testRebootlessUpdate_hasStagedSessionWithSameApex_fails() throws Exception { + assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); + + int sessionId = Install.single(APEX_V2).setStaged().commit(); + assertSessionReady(sessionId); + InstallUtils.commitExpectingFailure( + AssertionError.class, + "Staged session " + sessionId + " already contains " + SHIM_APEX_PACKAGE_NAME, + Install.single(APEX_V2)); + + } + private static void assertSessionApplied(int sessionId) { assertSessionState(sessionId, (session) -> { assertThat(session.isStagedSessionApplied()).isTrue(); diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java index 3bd3767ac6d95..5021009f65ae8 100644 --- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java @@ -43,7 +43,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.BufferedWriter; import java.io.File; +import java.io.FileWriter; import java.util.List; import java.util.stream.Collectors; @@ -60,6 +62,9 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { private static final String APK_A = "TestAppAv1.apk"; private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; + private static final String TEST_VENDOR_APEX_ALLOW_LIST = + "/vendor/etc/sysconfig/test-vendor-apex-allow-list.xml"; + private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this); /** @@ -87,7 +92,8 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", "/data/apex/active/" + SHIM_APEX_PACKAGE_NAME + "*.apex", "/system/apex/test.rebootless_apex_v1.apex", - "/data/apex/active/test.apex.rebootless*.apex"); + "/data/apex/active/test.apex.rebootless*.apex", + TEST_VENDOR_APEX_ALLOW_LIST); } @Before @@ -134,7 +140,23 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { } getDevice().remountSystemWritable(); assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName)); - getDevice().reboot(); + } + + private void pushTestVendorApexAllowList(String installerPackageName) throws Exception { + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + getDevice().remountSystemWritable(); + File file = File.createTempFile("test-vendor-apex-allow-list", ".xml"); + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + final String fmt = + "\n" + + " \n" + + ""; + writer.write(String.format(fmt, installerPackageName)); + } + getDevice().pushFile(file, TEST_VENDOR_APEX_ALLOW_LIST); } /** @@ -144,6 +166,8 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { @LargeTest public void testDuplicateApkInApexShouldFail() throws Exception { pushTestApex(APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"); + getDevice().reboot(); + runPhase("testDuplicateApkInApexShouldFail_Commit"); getDevice().reboot(); runPhase("testDuplicateApkInApexShouldFail_Verify"); @@ -388,12 +412,72 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { runPhase("testApexIsNotActivatedIfNotInCheckpointMode_VerifyPostReboot"); } + @Test + public void testApexInstallerNotInAllowListCanNotInstall() throws Exception { + assumeTrue("Device does not support updating APEX", + mHostUtils.isApexUpdateSupported()); + + runPhase("testApexInstallerNotInAllowListCanNotInstall_staged"); + runPhase("testApexInstallerNotInAllowListCanNotInstall_nonStaged"); + } + + @Test + @LargeTest + public void testApexNotInAllowListCanNotInstall() throws Exception { + assumeTrue("Device does not support updating APEX", + mHostUtils.isApexUpdateSupported()); + + pushTestApex("test.rebootless_apex_v1.apex"); + getDevice().reboot(); + + runPhase("testApexNotInAllowListCanNotInstall_staged"); + runPhase("testApexNotInAllowListCanNotInstall_nonStaged"); + } + + @Test + @LargeTest + public void testVendorApexWrongInstaller() throws Exception { + assumeTrue("Device does not support updating APEX", + mHostUtils.isApexUpdateSupported()); + + pushTestVendorApexAllowList("com.wrong.installer"); + pushTestApex("test.rebootless_apex_v1.apex"); + getDevice().reboot(); + + runPhase("testVendorApexWrongInstaller_staged"); + runPhase("testVendorApexWrongInstaller_nonStaged"); + } + + @Test + @LargeTest + public void testVendorApexCorrectInstaller() throws Exception { + assumeTrue("Device does not support updating APEX", + mHostUtils.isApexUpdateSupported()); + + pushTestVendorApexAllowList("com.android.tests.stagedinstallinternal"); + pushTestApex("test.rebootless_apex_v1.apex"); + getDevice().reboot(); + + runPhase("testVendorApexCorrectInstaller_staged"); + runPhase("testVendorApexCorrectInstaller_nonStaged"); + } + @Test public void testRebootlessUpdates() throws Exception { pushTestApex("test.rebootless_apex_v1.apex"); + getDevice().reboot(); + runPhase("testRebootlessUpdates"); } + @Test + public void testRebootlessUpdate_hasStagedSessionWithSameApex_fails() throws Exception { + assumeTrue("Device does not support updating APEX", + mHostUtils.isApexUpdateSupported()); + + runPhase("testRebootlessUpdate_hasStagedSessionWithSameApex_fails"); + } + private List getStagingDirectories() throws DeviceNotAvailableException { String baseDir = "/data/app-staging"; try {