diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 7cc49fd7aab24..542948491dc8c 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2260,6 +2260,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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) { 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/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 60585e84d4ef5..4684f0182d034 100644 --- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -57,6 +57,9 @@ public class StagedInstallInternalTest { private static final TestApp APEX_WRONG_SHA_V2 = new TestApp( "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(), @@ -388,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 c6b6aabb74ace..5021009f65ae8 100644 --- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java @@ -470,6 +470,14 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { 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 {