Loading services/core/java/com/android/server/pm/ApexManager.java +57 −4 Original line number Diff line number Diff line Loading @@ -60,7 +60,6 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; Loading Loading @@ -289,6 +288,21 @@ public abstract class ApexManager { */ abstract void registerApkInApex(AndroidPackage pkg); /** * Reports error raised during installation of apk-in-apex. * * @param scanDir the directory of the apex inside which apk-in-apex resides. */ abstract void reportErrorWithApkInApex(String scanDirPath); /** * Returns true if there were no errors when installing apk-in-apex inside * {@param apexPackageName}, otherwise false. * * @param apexPackageName Package name of the apk container of apex */ abstract boolean isApkInApexInstallSuccess(String apexPackageName); /** * Returns list of {@code packageName} of apks inside the given apex. * @param apexPackageName Package name of the apk container of apex Loading Loading @@ -368,6 +382,13 @@ public abstract class ApexManager { @GuardedBy("mLock") private ArrayMap<String, List<String>> mApksInApex = new ArrayMap<>(); /** * Contains the list of {@code Exception}s that were raised when installing apk-in-apex * inside {@code apexModuleName}. */ @GuardedBy("mLock") private Set<String> mErrorWithApkInApex = new ArraySet<>(); @GuardedBy("mLock") private List<PackageInfo> mAllPackagesCache; Loading Loading @@ -733,9 +754,7 @@ public abstract class ApexManager { @Override void registerApkInApex(AndroidPackage pkg) { synchronized (mLock) { final Iterator<ActiveApexInfo> it = mActiveApexInfosCache.iterator(); while (it.hasNext()) { final ActiveApexInfo aai = it.next(); for (ActiveApexInfo aai : mActiveApexInfosCache) { if (pkg.getBaseCodePath().startsWith(aai.apexDirectory.getAbsolutePath())) { List<String> apks = mApksInApex.get(aai.apexModuleName); if (apks == null) { Loading @@ -748,6 +767,30 @@ public abstract class ApexManager { } } @Override void reportErrorWithApkInApex(String scanDirPath) { synchronized (mLock) { for (ActiveApexInfo aai : mActiveApexInfosCache) { if (scanDirPath.startsWith(aai.apexDirectory.getAbsolutePath())) { mErrorWithApkInApex.add(aai.apexModuleName); } } } } @Override boolean isApkInApexInstallSuccess(String apexPackageName) { synchronized (mLock) { Preconditions.checkState(mPackageNameToApexModuleName != null, "APEX packages have not been scanned"); String moduleName = mPackageNameToApexModuleName.get(apexPackageName); if (moduleName == null) { return false; } return !mErrorWithApkInApex.contains(moduleName); } } @Override List<String> getApksInApex(String apexPackageName) { synchronized (mLock) { Loading Loading @@ -1039,6 +1082,16 @@ public abstract class ApexManager { // No-op } @Override void reportErrorWithApkInApex(String scanDirPath) { // No-op } @Override boolean isApkInApexInstallSuccess(String apexPackageName) { return true; } @Override List<String> getApksInApex(String apexPackageName) { return Collections.emptyList(); Loading services/core/java/com/android/server/pm/PackageManagerService.java +4 −0 Original line number Diff line number Diff line Loading @@ -8985,6 +8985,10 @@ public class PackageManagerService extends IPackageManager.Stub + parseResult.scanFile, throwable); } if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0 && errorCode != INSTALL_SUCCEEDED) { mApexManager.reportErrorWithApkInApex(scanDir.getAbsolutePath()); } // Delete invalid userdata apps if ((scanFlags & SCAN_AS_SYSTEM) == 0 && errorCode != PackageManager.INSTALL_SUCCEEDED) { services/core/java/com/android/server/pm/StagingManager.java +64 −29 Original line number Diff line number Diff line Loading @@ -370,24 +370,9 @@ public class StagingManager { } /** * Perform snapshot and restore as required both for APEXes themselves and for apks in APEX. * Apks inside apex are not installed using apk-install flow. They are scanned from the system * directory directly by PackageManager, as such, RollbackManager need to handle their data * separately here. * Utility function for extracting apex sessions out of multi-package/single session. */ private void snapshotAndRestoreForApexSession(PackageInstallerSession session) { if (!sessionContainsApex(session)) { return; } boolean doSnapshotOrRestore = (session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0 || session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK; if (!doSnapshotOrRestore) { return; } // Find all the apex sessions that needs processing private List<PackageInstallerSession> extractApexSessions(PackageInstallerSession session) { List<PackageInstallerSession> apexSessions = new ArrayList<>(); if (session.isMultiPackage()) { List<PackageInstallerSession> childrenSessions = new ArrayList<>(); Loading @@ -408,6 +393,50 @@ public class StagingManager { } else { apexSessions.add(session); } return apexSessions; } /** * Checks if all apk-in-apex were installed without errors for all of the apex sessions. Throws * error for any apk-in-apex failed to install. * * @throws PackageManagerException if any apk-in-apex failed to install */ private void checkInstallationOfApkInApexSuccessful(PackageInstallerSession session) throws PackageManagerException { final List<PackageInstallerSession> apexSessions = extractApexSessions(session); if (apexSessions.isEmpty()) { return; } for (PackageInstallerSession apexSession : apexSessions) { String packageName = apexSession.getPackageName(); if (!mApexManager.isApkInApexInstallSuccess(packageName)) { throw new PackageManagerException(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "Failed to install apk-in-apex of " + packageName); } } } /** * Perform snapshot and restore as required both for APEXes themselves and for apks in APEX. * Apks inside apex are not installed using apk-install flow. They are scanned from the system * directory directly by PackageManager, as such, RollbackManager need to handle their data * separately here. */ private void snapshotAndRestoreForApexSession(PackageInstallerSession session) { boolean doSnapshotOrRestore = (session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0 || session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK; if (!doSnapshotOrRestore) { return; } // Find all the apex sessions that needs processing final List<PackageInstallerSession> apexSessions = extractApexSessions(session); if (apexSessions.isEmpty()) { return; } final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class); final int[] allUsers = um.getUserIds(); Loading Loading @@ -545,18 +574,19 @@ public class StagingManager { return; } // Check if apex packages in the session failed to activate if (hasApex) { if (apexSessionInfo == null) { String errorMsg = "apexd did not know anything about a staged session supposed to" + " be activated"; final String errorMsg = "apexd did not know anything about a staged session " + "supposed to be activated"; session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg); abortCheckpoint(errorMsg); return; } if (isApexSessionFailed(apexSessionInfo)) { String errorMsg = "APEX activation failed. Check logcat messages from apexd for " + "more information."; String errorMsg = "APEX activation failed. Check logcat messages from apexd " + "for more information."; if (!TextUtils.isEmpty(mNativeFailureReason)) { errorMsg = "Session reverted due to crashing native process: " + mNativeFailureReason; Loading @@ -567,21 +597,26 @@ public class StagingManager { return; } if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) { // Apexd did not apply the session for some unknown reason. There is no guarantee // that apexd will install it next time. Safer to proactively mark as failed. String errorMsg = "Staged session " + session.sessionId + "at boot didn't " + "activate nor fail. Marking it as failed anyway."; // Apexd did not apply the session for some unknown reason. There is no // guarantee that apexd will install it next time. Safer to proactively mark // it as failed. final String errorMsg = "Staged session " + session.sessionId + "at boot " + "didn't activate nor fail. Marking it as failed anyway."; session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg); abortCheckpoint(errorMsg); return; } } // Handle apk and apk-in-apex installation try { if (hasApex) { checkInstallationOfApkInApexSuccessful(session); snapshotAndRestoreForApexSession(session); Slog.i(TAG, "APEX packages in session " + session.sessionId + " were successfully activated. Proceeding with APK packages, if any"); } // The APEX part of the session is activated, proceed with the installation of APKs. try { Slog.d(TAG, "Installing APK packages in session " + session.sessionId); installApksInSession(session); } catch (PackageManagerException e) { Loading services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java +16 −0 Original line number Diff line number Diff line Loading @@ -273,6 +273,21 @@ public class ApexManagerTest { assertThat(mApexManager.uninstallApex(TEST_APEX_PKG)).isFalse(); } @Test public void testReportErrorWithApkInApex() throws RemoteException { when(mApexService.getActivePackages()).thenReturn(createApexInfo(true, true)); final ApexManager.ActiveApexInfo activeApex = mApexManager.getActiveApexInfos().get(0); assertThat(activeApex.apexModuleName).isEqualTo(TEST_APEX_PKG); when(mApexService.getAllPackages()).thenReturn(createApexInfo(true, true)); mApexManager.scanApexPackagesTraced(mPackageParser2, ParallelPackageParser.makeExecutorService()); assertThat(mApexManager.isApkInApexInstallSuccess(activeApex.apexModuleName)).isTrue(); mApexManager.reportErrorWithApkInApex(activeApex.apexDirectory.getAbsolutePath()); assertThat(mApexManager.isApkInApexInstallSuccess(activeApex.apexModuleName)).isFalse(); } private ApexInfo[] createApexInfo(boolean isActive, boolean isFactory) { File apexFile = extractResource(TEST_APEX_PKG, TEST_APEX_FILE_NAME); ApexInfo apexInfo = new ApexInfo(); Loading @@ -281,6 +296,7 @@ public class ApexManagerTest { apexInfo.moduleName = TEST_APEX_PKG; apexInfo.modulePath = apexFile.getPath(); apexInfo.versionCode = 191000070; apexInfo.preinstalledModulePath = apexFile.getPath(); return new ApexInfo[]{apexInfo}; } Loading Loading
services/core/java/com/android/server/pm/ApexManager.java +57 −4 Original line number Diff line number Diff line Loading @@ -60,7 +60,6 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; Loading Loading @@ -289,6 +288,21 @@ public abstract class ApexManager { */ abstract void registerApkInApex(AndroidPackage pkg); /** * Reports error raised during installation of apk-in-apex. * * @param scanDir the directory of the apex inside which apk-in-apex resides. */ abstract void reportErrorWithApkInApex(String scanDirPath); /** * Returns true if there were no errors when installing apk-in-apex inside * {@param apexPackageName}, otherwise false. * * @param apexPackageName Package name of the apk container of apex */ abstract boolean isApkInApexInstallSuccess(String apexPackageName); /** * Returns list of {@code packageName} of apks inside the given apex. * @param apexPackageName Package name of the apk container of apex Loading Loading @@ -368,6 +382,13 @@ public abstract class ApexManager { @GuardedBy("mLock") private ArrayMap<String, List<String>> mApksInApex = new ArrayMap<>(); /** * Contains the list of {@code Exception}s that were raised when installing apk-in-apex * inside {@code apexModuleName}. */ @GuardedBy("mLock") private Set<String> mErrorWithApkInApex = new ArraySet<>(); @GuardedBy("mLock") private List<PackageInfo> mAllPackagesCache; Loading Loading @@ -733,9 +754,7 @@ public abstract class ApexManager { @Override void registerApkInApex(AndroidPackage pkg) { synchronized (mLock) { final Iterator<ActiveApexInfo> it = mActiveApexInfosCache.iterator(); while (it.hasNext()) { final ActiveApexInfo aai = it.next(); for (ActiveApexInfo aai : mActiveApexInfosCache) { if (pkg.getBaseCodePath().startsWith(aai.apexDirectory.getAbsolutePath())) { List<String> apks = mApksInApex.get(aai.apexModuleName); if (apks == null) { Loading @@ -748,6 +767,30 @@ public abstract class ApexManager { } } @Override void reportErrorWithApkInApex(String scanDirPath) { synchronized (mLock) { for (ActiveApexInfo aai : mActiveApexInfosCache) { if (scanDirPath.startsWith(aai.apexDirectory.getAbsolutePath())) { mErrorWithApkInApex.add(aai.apexModuleName); } } } } @Override boolean isApkInApexInstallSuccess(String apexPackageName) { synchronized (mLock) { Preconditions.checkState(mPackageNameToApexModuleName != null, "APEX packages have not been scanned"); String moduleName = mPackageNameToApexModuleName.get(apexPackageName); if (moduleName == null) { return false; } return !mErrorWithApkInApex.contains(moduleName); } } @Override List<String> getApksInApex(String apexPackageName) { synchronized (mLock) { Loading Loading @@ -1039,6 +1082,16 @@ public abstract class ApexManager { // No-op } @Override void reportErrorWithApkInApex(String scanDirPath) { // No-op } @Override boolean isApkInApexInstallSuccess(String apexPackageName) { return true; } @Override List<String> getApksInApex(String apexPackageName) { return Collections.emptyList(); Loading
services/core/java/com/android/server/pm/PackageManagerService.java +4 −0 Original line number Diff line number Diff line Loading @@ -8985,6 +8985,10 @@ public class PackageManagerService extends IPackageManager.Stub + parseResult.scanFile, throwable); } if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0 && errorCode != INSTALL_SUCCEEDED) { mApexManager.reportErrorWithApkInApex(scanDir.getAbsolutePath()); } // Delete invalid userdata apps if ((scanFlags & SCAN_AS_SYSTEM) == 0 && errorCode != PackageManager.INSTALL_SUCCEEDED) {
services/core/java/com/android/server/pm/StagingManager.java +64 −29 Original line number Diff line number Diff line Loading @@ -370,24 +370,9 @@ public class StagingManager { } /** * Perform snapshot and restore as required both for APEXes themselves and for apks in APEX. * Apks inside apex are not installed using apk-install flow. They are scanned from the system * directory directly by PackageManager, as such, RollbackManager need to handle their data * separately here. * Utility function for extracting apex sessions out of multi-package/single session. */ private void snapshotAndRestoreForApexSession(PackageInstallerSession session) { if (!sessionContainsApex(session)) { return; } boolean doSnapshotOrRestore = (session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0 || session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK; if (!doSnapshotOrRestore) { return; } // Find all the apex sessions that needs processing private List<PackageInstallerSession> extractApexSessions(PackageInstallerSession session) { List<PackageInstallerSession> apexSessions = new ArrayList<>(); if (session.isMultiPackage()) { List<PackageInstallerSession> childrenSessions = new ArrayList<>(); Loading @@ -408,6 +393,50 @@ public class StagingManager { } else { apexSessions.add(session); } return apexSessions; } /** * Checks if all apk-in-apex were installed without errors for all of the apex sessions. Throws * error for any apk-in-apex failed to install. * * @throws PackageManagerException if any apk-in-apex failed to install */ private void checkInstallationOfApkInApexSuccessful(PackageInstallerSession session) throws PackageManagerException { final List<PackageInstallerSession> apexSessions = extractApexSessions(session); if (apexSessions.isEmpty()) { return; } for (PackageInstallerSession apexSession : apexSessions) { String packageName = apexSession.getPackageName(); if (!mApexManager.isApkInApexInstallSuccess(packageName)) { throw new PackageManagerException(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "Failed to install apk-in-apex of " + packageName); } } } /** * Perform snapshot and restore as required both for APEXes themselves and for apks in APEX. * Apks inside apex are not installed using apk-install flow. They are scanned from the system * directory directly by PackageManager, as such, RollbackManager need to handle their data * separately here. */ private void snapshotAndRestoreForApexSession(PackageInstallerSession session) { boolean doSnapshotOrRestore = (session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0 || session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK; if (!doSnapshotOrRestore) { return; } // Find all the apex sessions that needs processing final List<PackageInstallerSession> apexSessions = extractApexSessions(session); if (apexSessions.isEmpty()) { return; } final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class); final int[] allUsers = um.getUserIds(); Loading Loading @@ -545,18 +574,19 @@ public class StagingManager { return; } // Check if apex packages in the session failed to activate if (hasApex) { if (apexSessionInfo == null) { String errorMsg = "apexd did not know anything about a staged session supposed to" + " be activated"; final String errorMsg = "apexd did not know anything about a staged session " + "supposed to be activated"; session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg); abortCheckpoint(errorMsg); return; } if (isApexSessionFailed(apexSessionInfo)) { String errorMsg = "APEX activation failed. Check logcat messages from apexd for " + "more information."; String errorMsg = "APEX activation failed. Check logcat messages from apexd " + "for more information."; if (!TextUtils.isEmpty(mNativeFailureReason)) { errorMsg = "Session reverted due to crashing native process: " + mNativeFailureReason; Loading @@ -567,21 +597,26 @@ public class StagingManager { return; } if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) { // Apexd did not apply the session for some unknown reason. There is no guarantee // that apexd will install it next time. Safer to proactively mark as failed. String errorMsg = "Staged session " + session.sessionId + "at boot didn't " + "activate nor fail. Marking it as failed anyway."; // Apexd did not apply the session for some unknown reason. There is no // guarantee that apexd will install it next time. Safer to proactively mark // it as failed. final String errorMsg = "Staged session " + session.sessionId + "at boot " + "didn't activate nor fail. Marking it as failed anyway."; session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg); abortCheckpoint(errorMsg); return; } } // Handle apk and apk-in-apex installation try { if (hasApex) { checkInstallationOfApkInApexSuccessful(session); snapshotAndRestoreForApexSession(session); Slog.i(TAG, "APEX packages in session " + session.sessionId + " were successfully activated. Proceeding with APK packages, if any"); } // The APEX part of the session is activated, proceed with the installation of APKs. try { Slog.d(TAG, "Installing APK packages in session " + session.sessionId); installApksInSession(session); } catch (PackageManagerException e) { Loading
services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java +16 −0 Original line number Diff line number Diff line Loading @@ -273,6 +273,21 @@ public class ApexManagerTest { assertThat(mApexManager.uninstallApex(TEST_APEX_PKG)).isFalse(); } @Test public void testReportErrorWithApkInApex() throws RemoteException { when(mApexService.getActivePackages()).thenReturn(createApexInfo(true, true)); final ApexManager.ActiveApexInfo activeApex = mApexManager.getActiveApexInfos().get(0); assertThat(activeApex.apexModuleName).isEqualTo(TEST_APEX_PKG); when(mApexService.getAllPackages()).thenReturn(createApexInfo(true, true)); mApexManager.scanApexPackagesTraced(mPackageParser2, ParallelPackageParser.makeExecutorService()); assertThat(mApexManager.isApkInApexInstallSuccess(activeApex.apexModuleName)).isTrue(); mApexManager.reportErrorWithApkInApex(activeApex.apexDirectory.getAbsolutePath()); assertThat(mApexManager.isApkInApexInstallSuccess(activeApex.apexModuleName)).isFalse(); } private ApexInfo[] createApexInfo(boolean isActive, boolean isFactory) { File apexFile = extractResource(TEST_APEX_PKG, TEST_APEX_FILE_NAME); ApexInfo apexInfo = new ApexInfo(); Loading @@ -281,6 +296,7 @@ public class ApexManagerTest { apexInfo.moduleName = TEST_APEX_PKG; apexInfo.modulePath = apexFile.getPath(); apexInfo.versionCode = 191000070; apexInfo.preinstalledModulePath = apexFile.getPath(); return new ApexInfo[]{apexInfo}; } Loading