Merge "Fail staged install if any apk-in-apex fails to install" into rvc-dev am: f34e29e181
Change-Id: Iefd03e2486f8cd2f173c16ac61f82855763cbc1e
This commit is contained in:
committed by
Automerger Merge Worker
commit
a276d6f183
@@ -60,7 +60,6 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -289,6 +288,21 @@ public abstract class ApexManager {
|
|||||||
*/
|
*/
|
||||||
abstract void registerApkInApex(AndroidPackage pkg);
|
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.
|
* Returns list of {@code packageName} of apks inside the given apex.
|
||||||
* @param apexPackageName Package name of the apk container of apex
|
* @param apexPackageName Package name of the apk container of apex
|
||||||
@@ -368,6 +382,13 @@ public abstract class ApexManager {
|
|||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private ArrayMap<String, List<String>> mApksInApex = new ArrayMap<>();
|
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")
|
@GuardedBy("mLock")
|
||||||
private List<PackageInfo> mAllPackagesCache;
|
private List<PackageInfo> mAllPackagesCache;
|
||||||
|
|
||||||
@@ -733,9 +754,7 @@ public abstract class ApexManager {
|
|||||||
@Override
|
@Override
|
||||||
void registerApkInApex(AndroidPackage pkg) {
|
void registerApkInApex(AndroidPackage pkg) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
final Iterator<ActiveApexInfo> it = mActiveApexInfosCache.iterator();
|
for (ActiveApexInfo aai : mActiveApexInfosCache) {
|
||||||
while (it.hasNext()) {
|
|
||||||
final ActiveApexInfo aai = it.next();
|
|
||||||
if (pkg.getBaseCodePath().startsWith(aai.apexDirectory.getAbsolutePath())) {
|
if (pkg.getBaseCodePath().startsWith(aai.apexDirectory.getAbsolutePath())) {
|
||||||
List<String> apks = mApksInApex.get(aai.apexModuleName);
|
List<String> apks = mApksInApex.get(aai.apexModuleName);
|
||||||
if (apks == null) {
|
if (apks == null) {
|
||||||
@@ -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
|
@Override
|
||||||
List<String> getApksInApex(String apexPackageName) {
|
List<String> getApksInApex(String apexPackageName) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
@@ -1039,6 +1082,16 @@ public abstract class ApexManager {
|
|||||||
// No-op
|
// No-op
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void reportErrorWithApkInApex(String scanDirPath) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isApkInApexInstallSuccess(String apexPackageName) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
List<String> getApksInApex(String apexPackageName) {
|
List<String> getApksInApex(String apexPackageName) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
|||||||
@@ -8985,6 +8985,10 @@ public class PackageManagerService extends IPackageManager.Stub
|
|||||||
+ parseResult.scanFile, throwable);
|
+ parseResult.scanFile, throwable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0 && errorCode != INSTALL_SUCCEEDED) {
|
||||||
|
mApexManager.reportErrorWithApkInApex(scanDir.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
// Delete invalid userdata apps
|
// Delete invalid userdata apps
|
||||||
if ((scanFlags & SCAN_AS_SYSTEM) == 0
|
if ((scanFlags & SCAN_AS_SYSTEM) == 0
|
||||||
&& errorCode != PackageManager.INSTALL_SUCCEEDED) {
|
&& errorCode != PackageManager.INSTALL_SUCCEEDED) {
|
||||||
|
|||||||
@@ -370,24 +370,9 @@ public class StagingManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform snapshot and restore as required both for APEXes themselves and for apks in APEX.
|
* Utility function for extracting apex sessions out of multi-package/single session.
|
||||||
* 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) {
|
private List<PackageInstallerSession> extractApexSessions(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
|
|
||||||
List<PackageInstallerSession> apexSessions = new ArrayList<>();
|
List<PackageInstallerSession> apexSessions = new ArrayList<>();
|
||||||
if (session.isMultiPackage()) {
|
if (session.isMultiPackage()) {
|
||||||
List<PackageInstallerSession> childrenSessions = new ArrayList<>();
|
List<PackageInstallerSession> childrenSessions = new ArrayList<>();
|
||||||
@@ -408,6 +393,50 @@ public class StagingManager {
|
|||||||
} else {
|
} else {
|
||||||
apexSessions.add(session);
|
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 UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
|
||||||
final int[] allUsers = um.getUserIds();
|
final int[] allUsers = um.getUserIds();
|
||||||
@@ -545,18 +574,19 @@ public class StagingManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if apex packages in the session failed to activate
|
||||||
if (hasApex) {
|
if (hasApex) {
|
||||||
if (apexSessionInfo == null) {
|
if (apexSessionInfo == null) {
|
||||||
String errorMsg = "apexd did not know anything about a staged session supposed to"
|
final String errorMsg = "apexd did not know anything about a staged session "
|
||||||
+ " be activated";
|
+ "supposed to be activated";
|
||||||
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
|
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
|
||||||
errorMsg);
|
errorMsg);
|
||||||
abortCheckpoint(errorMsg);
|
abortCheckpoint(errorMsg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isApexSessionFailed(apexSessionInfo)) {
|
if (isApexSessionFailed(apexSessionInfo)) {
|
||||||
String errorMsg = "APEX activation failed. Check logcat messages from apexd for "
|
String errorMsg = "APEX activation failed. Check logcat messages from apexd "
|
||||||
+ "more information.";
|
+ "for more information.";
|
||||||
if (!TextUtils.isEmpty(mNativeFailureReason)) {
|
if (!TextUtils.isEmpty(mNativeFailureReason)) {
|
||||||
errorMsg = "Session reverted due to crashing native process: "
|
errorMsg = "Session reverted due to crashing native process: "
|
||||||
+ mNativeFailureReason;
|
+ mNativeFailureReason;
|
||||||
@@ -567,21 +597,26 @@ public class StagingManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
|
if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
|
||||||
// Apexd did not apply the session for some unknown reason. There is no guarantee
|
// Apexd did not apply the session for some unknown reason. There is no
|
||||||
// that apexd will install it next time. Safer to proactively mark as failed.
|
// guarantee that apexd will install it next time. Safer to proactively mark
|
||||||
String errorMsg = "Staged session " + session.sessionId + "at boot didn't "
|
// it as failed.
|
||||||
+ "activate nor fail. Marking it as failed anyway.";
|
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,
|
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
|
||||||
errorMsg);
|
errorMsg);
|
||||||
abortCheckpoint(errorMsg);
|
abortCheckpoint(errorMsg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// Handle apk and apk-in-apex installation
|
||||||
|
try {
|
||||||
|
if (hasApex) {
|
||||||
|
checkInstallationOfApkInApexSuccessful(session);
|
||||||
snapshotAndRestoreForApexSession(session);
|
snapshotAndRestoreForApexSession(session);
|
||||||
Slog.i(TAG, "APEX packages in session " + session.sessionId
|
Slog.i(TAG, "APEX packages in session " + session.sessionId
|
||||||
+ " were successfully activated. Proceeding with APK packages, if any");
|
+ " were successfully activated. Proceeding with APK packages, if any");
|
||||||
}
|
}
|
||||||
// The APEX part of the session is activated, proceed with the installation of APKs.
|
// 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);
|
Slog.d(TAG, "Installing APK packages in session " + session.sessionId);
|
||||||
installApksInSession(session);
|
installApksInSession(session);
|
||||||
} catch (PackageManagerException e) {
|
} catch (PackageManagerException e) {
|
||||||
|
|||||||
@@ -273,6 +273,21 @@ public class ApexManagerTest {
|
|||||||
assertThat(mApexManager.uninstallApex(TEST_APEX_PKG)).isFalse();
|
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) {
|
private ApexInfo[] createApexInfo(boolean isActive, boolean isFactory) {
|
||||||
File apexFile = extractResource(TEST_APEX_PKG, TEST_APEX_FILE_NAME);
|
File apexFile = extractResource(TEST_APEX_PKG, TEST_APEX_FILE_NAME);
|
||||||
ApexInfo apexInfo = new ApexInfo();
|
ApexInfo apexInfo = new ApexInfo();
|
||||||
@@ -281,6 +296,7 @@ public class ApexManagerTest {
|
|||||||
apexInfo.moduleName = TEST_APEX_PKG;
|
apexInfo.moduleName = TEST_APEX_PKG;
|
||||||
apexInfo.modulePath = apexFile.getPath();
|
apexInfo.modulePath = apexFile.getPath();
|
||||||
apexInfo.versionCode = 191000070;
|
apexInfo.versionCode = 191000070;
|
||||||
|
apexInfo.preinstalledModulePath = apexFile.getPath();
|
||||||
|
|
||||||
return new ApexInfo[]{apexInfo};
|
return new ApexInfo[]{apexInfo};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user