Loading services/core/java/com/android/server/pm/PackageInstallerSession.java +9 −1 Original line number Diff line number Diff line Loading @@ -1637,7 +1637,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * If session should be sealed, then it's sealed to prevent further modification. * If the session can't be sealed then it's destroyed. * * Additionally for staged APEX sessions read+validate the package and populate req'd fields. * Additionally for staged APEX/APK sessions read+validate the package and populate req'd * fields. * * <p> This is meant to be called after all of the sessions are loaded and added to * PackageInstallerService Loading Loading @@ -1670,6 +1671,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // APEX installations rely on certain fields to be populated after reboot. // E.g. mPackageName. validateApexInstallLocked(); } else { // Populate mPackageName for this APK session which is required by the staging // manager to check duplicate apk-in-apex. PackageInstallerSession parent = allSessions.get(mParentSessionId); if (parent != null && parent.isStagedSessionReady()) { validateApkInstallLocked(); } } } catch (PackageManagerException e) { Slog.e(TAG, "Package not valid", e); Loading services/core/java/com/android/server/pm/StagingManager.java +37 −0 Original line number Diff line number Diff line Loading @@ -56,6 +56,7 @@ import android.os.UserManagerInternal; import android.os.storage.IStorageManager; import android.os.storage.StorageManager; import android.text.TextUtils; import android.util.ArraySet; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; Loading Loading @@ -84,6 +85,7 @@ import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; Loading Loading @@ -650,6 +652,7 @@ public class StagingManager { try { if (hasApex) { checkInstallationOfApkInApexSuccessful(session); checkDuplicateApkInApex(session); snapshotAndRestoreForApexSession(session); Slog.i(TAG, "APEX packages in session " + session.sessionId + " were successfully activated. Proceeding with APK packages, if any"); Loading Loading @@ -829,6 +832,40 @@ public class StagingManager { return null; } /** * Throws a PackageManagerException if there are duplicate packages in apk and apk-in-apex. */ private void checkDuplicateApkInApex(@NonNull PackageInstallerSession session) throws PackageManagerException { if (!session.isMultiPackage()) { return; } final int[] childSessionIds = session.getChildSessionIds(); final Set<String> apkNames = new ArraySet<>(); synchronized (mStagedSessions) { for (int id : childSessionIds) { final PackageInstallerSession s = mStagedSessions.get(id); if (!isApexSession(s)) { apkNames.add(s.getPackageName()); } } } final List<PackageInstallerSession> apexSessions = extractApexSessions(session); for (PackageInstallerSession apexSession : apexSessions) { String packageName = apexSession.getPackageName(); for (String apkInApex : mApexManager.getApksInApex(packageName)) { if (!apkNames.add(apkInApex)) { throw new PackageManagerException( SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "Package: " + packageName + " in session: " + apexSession.sessionId + " has duplicate apk-in-apex: " + apkInApex, null); } } } } private void installApksInSession(@NonNull PackageInstallerSession session) throws PackageManagerException { Loading tests/StagedInstallTest/Android.bp +3 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,9 @@ android_test_helper_app { srcs: ["app/src/**/*.java"], static_libs: ["androidx.test.rules", "cts-install-lib"], test_suites: ["general-tests"], java_resources: [ ":com.android.apex.apkrollback.test_v2", ], } java_test_host { Loading tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +23 −0 Original line number Diff line number Diff line Loading @@ -49,6 +49,11 @@ import java.util.function.Consumer; public class StagedInstallInternalTest { private static final String TAG = StagedInstallInternalTest.class.getSimpleName(); private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; private static final TestApp TEST_APEX_WITH_APK_V1 = new TestApp("TestApexWithApkV1", APK_IN_APEX_TESTAPEX_NAME, 1, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"); 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 File mTestStateFile = new File( InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(), Loading Loading @@ -81,6 +86,24 @@ public class StagedInstallInternalTest { Files.deleteIfExists(mTestStateFile.toPath()); } @Test public void testDuplicateApkInApexShouldFail_Commit() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); // Duplicate packages(TestApp.A) in TEST_APEX_WITH_APK_V2(apk-in-apex) and TestApp.A2(apk) // should fail to install. int sessionId = Install.multi(TEST_APEX_WITH_APK_V2, TestApp.A2).setStaged().commit(); storeSessionId(sessionId); } @Test public void testDuplicateApkInApexShouldFail_Verify() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); int sessionId = retrieveLastSessionId(); PackageInstaller.SessionInfo info = InstallUtils.getPackageInstaller().getSessionInfo(sessionId); assertThat(info.isStagedSessionFailed()).isTrue(); } @Test public void testSystemServerRestartDoesNotAffectStagedSessions_Commit() throws Exception { int sessionId = Install.single(TestApp.A1).setStaged().commit(); Loading tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java +55 −0 Original line number Diff line number Diff line Loading @@ -24,11 +24,14 @@ import static org.junit.Assume.assumeTrue; import android.cts.install.lib.host.InstallUtilsHost; import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; import com.android.ddmlib.Log; import com.android.tests.rollback.host.AbandonSessionsRule; import com.android.tests.util.ModuleTestUtils; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.CommandStatus; import com.android.tradefed.util.ProcessInfo; import org.junit.After; Loading @@ -49,6 +52,7 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this); private static final String SHIM_V2 = "com.android.apex.cts.shim.v2.apex"; private static final String APK_A = "TestAppAv1.apk"; private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; private final ModuleTestUtils mTestUtils = new ModuleTestUtils(this); private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this); Loading @@ -74,6 +78,8 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { } catch (AssertionError e) { Log.e(TAG, e); } deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex"); } @Before Loading @@ -86,6 +92,55 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { cleanUp(); } /** * Deletes files and reboots the device if necessary. * @param files the paths of files which might contain wildcards */ private void deleteFiles(String... files) throws Exception { boolean found = false; for (String file : files) { CommandResult result = getDevice().executeShellV2Command("ls " + file); if (result.getStatus() == CommandStatus.SUCCESS) { found = true; break; } } if (found) { if (!getDevice().isAdbRoot()) { getDevice().enableAdbRoot(); } getDevice().remountSystemWritable(); for (String file : files) { getDevice().executeShellCommand("rm -rf " + file); } getDevice().reboot(); } } private void pushTestApex() throws Exception { CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild()); final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"; final File apex = buildHelper.getTestFile(fileName); if (!getDevice().isAdbRoot()) { getDevice().enableAdbRoot(); } getDevice().remountSystemWritable(); assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName)); getDevice().reboot(); } /** * Tests that duplicate packages in apk-in-apex and apk should fail to install. */ @Test public void testDuplicateApkInApexShouldFail() throws Exception { pushTestApex(); runPhase("testDuplicateApkInApexShouldFail_Commit"); getDevice().reboot(); runPhase("testDuplicateApkInApexShouldFail_Verify"); } @Test public void testSystemServerRestartDoesNotAffectStagedSessions() throws Exception { runPhase("testSystemServerRestartDoesNotAffectStagedSessions_Commit"); Loading Loading
services/core/java/com/android/server/pm/PackageInstallerSession.java +9 −1 Original line number Diff line number Diff line Loading @@ -1637,7 +1637,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * If session should be sealed, then it's sealed to prevent further modification. * If the session can't be sealed then it's destroyed. * * Additionally for staged APEX sessions read+validate the package and populate req'd fields. * Additionally for staged APEX/APK sessions read+validate the package and populate req'd * fields. * * <p> This is meant to be called after all of the sessions are loaded and added to * PackageInstallerService Loading Loading @@ -1670,6 +1671,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // APEX installations rely on certain fields to be populated after reboot. // E.g. mPackageName. validateApexInstallLocked(); } else { // Populate mPackageName for this APK session which is required by the staging // manager to check duplicate apk-in-apex. PackageInstallerSession parent = allSessions.get(mParentSessionId); if (parent != null && parent.isStagedSessionReady()) { validateApkInstallLocked(); } } } catch (PackageManagerException e) { Slog.e(TAG, "Package not valid", e); Loading
services/core/java/com/android/server/pm/StagingManager.java +37 −0 Original line number Diff line number Diff line Loading @@ -56,6 +56,7 @@ import android.os.UserManagerInternal; import android.os.storage.IStorageManager; import android.os.storage.StorageManager; import android.text.TextUtils; import android.util.ArraySet; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; Loading Loading @@ -84,6 +85,7 @@ import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; Loading Loading @@ -650,6 +652,7 @@ public class StagingManager { try { if (hasApex) { checkInstallationOfApkInApexSuccessful(session); checkDuplicateApkInApex(session); snapshotAndRestoreForApexSession(session); Slog.i(TAG, "APEX packages in session " + session.sessionId + " were successfully activated. Proceeding with APK packages, if any"); Loading Loading @@ -829,6 +832,40 @@ public class StagingManager { return null; } /** * Throws a PackageManagerException if there are duplicate packages in apk and apk-in-apex. */ private void checkDuplicateApkInApex(@NonNull PackageInstallerSession session) throws PackageManagerException { if (!session.isMultiPackage()) { return; } final int[] childSessionIds = session.getChildSessionIds(); final Set<String> apkNames = new ArraySet<>(); synchronized (mStagedSessions) { for (int id : childSessionIds) { final PackageInstallerSession s = mStagedSessions.get(id); if (!isApexSession(s)) { apkNames.add(s.getPackageName()); } } } final List<PackageInstallerSession> apexSessions = extractApexSessions(session); for (PackageInstallerSession apexSession : apexSessions) { String packageName = apexSession.getPackageName(); for (String apkInApex : mApexManager.getApksInApex(packageName)) { if (!apkNames.add(apkInApex)) { throw new PackageManagerException( SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "Package: " + packageName + " in session: " + apexSession.sessionId + " has duplicate apk-in-apex: " + apkInApex, null); } } } } private void installApksInSession(@NonNull PackageInstallerSession session) throws PackageManagerException { Loading
tests/StagedInstallTest/Android.bp +3 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,9 @@ android_test_helper_app { srcs: ["app/src/**/*.java"], static_libs: ["androidx.test.rules", "cts-install-lib"], test_suites: ["general-tests"], java_resources: [ ":com.android.apex.apkrollback.test_v2", ], } java_test_host { Loading
tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +23 −0 Original line number Diff line number Diff line Loading @@ -49,6 +49,11 @@ import java.util.function.Consumer; public class StagedInstallInternalTest { private static final String TAG = StagedInstallInternalTest.class.getSimpleName(); private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; private static final TestApp TEST_APEX_WITH_APK_V1 = new TestApp("TestApexWithApkV1", APK_IN_APEX_TESTAPEX_NAME, 1, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"); 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 File mTestStateFile = new File( InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(), Loading Loading @@ -81,6 +86,24 @@ public class StagedInstallInternalTest { Files.deleteIfExists(mTestStateFile.toPath()); } @Test public void testDuplicateApkInApexShouldFail_Commit() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); // Duplicate packages(TestApp.A) in TEST_APEX_WITH_APK_V2(apk-in-apex) and TestApp.A2(apk) // should fail to install. int sessionId = Install.multi(TEST_APEX_WITH_APK_V2, TestApp.A2).setStaged().commit(); storeSessionId(sessionId); } @Test public void testDuplicateApkInApexShouldFail_Verify() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); int sessionId = retrieveLastSessionId(); PackageInstaller.SessionInfo info = InstallUtils.getPackageInstaller().getSessionInfo(sessionId); assertThat(info.isStagedSessionFailed()).isTrue(); } @Test public void testSystemServerRestartDoesNotAffectStagedSessions_Commit() throws Exception { int sessionId = Install.single(TestApp.A1).setStaged().commit(); Loading
tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java +55 −0 Original line number Diff line number Diff line Loading @@ -24,11 +24,14 @@ import static org.junit.Assume.assumeTrue; import android.cts.install.lib.host.InstallUtilsHost; import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; import com.android.ddmlib.Log; import com.android.tests.rollback.host.AbandonSessionsRule; import com.android.tests.util.ModuleTestUtils; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.CommandStatus; import com.android.tradefed.util.ProcessInfo; import org.junit.After; Loading @@ -49,6 +52,7 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this); private static final String SHIM_V2 = "com.android.apex.cts.shim.v2.apex"; private static final String APK_A = "TestAppAv1.apk"; private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; private final ModuleTestUtils mTestUtils = new ModuleTestUtils(this); private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this); Loading @@ -74,6 +78,8 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { } catch (AssertionError e) { Log.e(TAG, e); } deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex"); } @Before Loading @@ -86,6 +92,55 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { cleanUp(); } /** * Deletes files and reboots the device if necessary. * @param files the paths of files which might contain wildcards */ private void deleteFiles(String... files) throws Exception { boolean found = false; for (String file : files) { CommandResult result = getDevice().executeShellV2Command("ls " + file); if (result.getStatus() == CommandStatus.SUCCESS) { found = true; break; } } if (found) { if (!getDevice().isAdbRoot()) { getDevice().enableAdbRoot(); } getDevice().remountSystemWritable(); for (String file : files) { getDevice().executeShellCommand("rm -rf " + file); } getDevice().reboot(); } } private void pushTestApex() throws Exception { CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild()); final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"; final File apex = buildHelper.getTestFile(fileName); if (!getDevice().isAdbRoot()) { getDevice().enableAdbRoot(); } getDevice().remountSystemWritable(); assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName)); getDevice().reboot(); } /** * Tests that duplicate packages in apk-in-apex and apk should fail to install. */ @Test public void testDuplicateApkInApexShouldFail() throws Exception { pushTestApex(); runPhase("testDuplicateApkInApexShouldFail_Commit"); getDevice().reboot(); runPhase("testDuplicateApkInApexShouldFail_Verify"); } @Test public void testSystemServerRestartDoesNotAffectStagedSessions() throws Exception { runPhase("testSystemServerRestartDoesNotAffectStagedSessions_Commit"); Loading