Added high impact rollback in RollbackPackageHealthObserver
Added high impact rollback as a last resort after low impact rollbacks
are tried. Also removed reference to automatic rollback deny list, since
we will be using impact level instead of the deny list going forward.
Bug: 291137901
Test: Unit tests
Change-Id: I4d75dd513b3f43c179baf93ece832f9007173ff4
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
index b5cf011..ce8fb65 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
@@ -100,13 +100,15 @@
public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2;
public static final int FAILURE_REASON_APP_CRASH = 3;
public static final int FAILURE_REASON_APP_NOT_RESPONDING = 4;
+ public static final int FAILURE_REASON_BOOT_LOOP = 5;
@IntDef(prefix = { "FAILURE_REASON_" }, value = {
FAILURE_REASON_UNKNOWN,
FAILURE_REASON_NATIVE_CRASH,
FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
FAILURE_REASON_APP_CRASH,
- FAILURE_REASON_APP_NOT_RESPONDING
+ FAILURE_REASON_APP_NOT_RESPONDING,
+ FAILURE_REASON_BOOT_LOOP
})
@Retention(RetentionPolicy.SOURCE)
public @interface FailureReasons {}
@@ -542,7 +544,7 @@
mNumberOfNativeCrashPollsRemaining--;
// Check if native watchdog reported a crash
if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
- // We rollback everything available when crash is unattributable
+ // We rollback all available low impact rollbacks when crash is unattributable
onPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH);
// we stop polling after an attempt to execute rollback, regardless of whether the
// attempt succeeds or not
@@ -572,6 +574,7 @@
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_50,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_90,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100})
public @interface PackageHealthObserverImpact {
/** No action to take. */
@@ -582,6 +585,7 @@
int USER_IMPACT_LEVEL_30 = 30;
int USER_IMPACT_LEVEL_50 = 50;
int USER_IMPACT_LEVEL_70 = 70;
+ int USER_IMPACT_LEVEL_90 = 90;
/* Action has high user impact, a last resort, user of a device will be very frustrated. */
int USER_IMPACT_LEVEL_100 = 100;
}
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index dd74a2a..5fb47dd 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -28,6 +28,7 @@
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
+import android.crashrecovery.flags.Flags;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
@@ -45,7 +46,6 @@
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
-import com.android.server.SystemConfig;
import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
import com.android.server.pm.ApexManager;
@@ -57,6 +57,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@@ -84,7 +85,8 @@
// True if needing to roll back only rebootless apexes when native crash happens
private boolean mTwoPhaseRollbackEnabled;
- RollbackPackageHealthObserver(Context context) {
+ @VisibleForTesting
+ RollbackPackageHealthObserver(Context context, ApexManager apexManager) {
mContext = context;
HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
handlerThread.start();
@@ -94,7 +96,7 @@
mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
- mApexManager = ApexManager.getInstance();
+ mApexManager = apexManager;
if (SystemProperties.getBoolean("sys.boot_completed", false)) {
// Load the value from the file if system server has crashed and restarted
@@ -107,24 +109,46 @@
}
}
+ RollbackPackageHealthObserver(Context context) {
+ this(context, ApexManager.getInstance());
+ }
+
@Override
public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
@FailureReasons int failureReason, int mitigationCount) {
- boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class)
- .getAvailableRollbacks().isEmpty();
int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+ List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
+ availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ if (!lowImpactRollbacks.isEmpty()) {
+ if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ // For native crashes, we will directly roll back any available rollbacks at low
+ // impact level
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else if (getRollbackForPackage(failedPackage, lowImpactRollbacks) != null) {
+ // Rollback is available for crashing low impact package
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else {
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
+ }
+ }
+ } else {
+ boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class)
+ .getAvailableRollbacks().isEmpty();
- if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
- && anyRollbackAvailable) {
- // For native crashes, we will directly roll back any available rollbacks
- // Note: For non-native crashes the rollback-all step has higher impact
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
- } else if (getAvailableRollback(failedPackage) != null) {
- // Rollback is available, we may get a callback into #execute
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
- } else if (anyRollbackAvailable) {
- // If any rollbacks are available, we will commit them
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
+ if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
+ && anyRollbackAvailable) {
+ // For native crashes, we will directly roll back any available rollbacks
+ // Note: For non-native crashes the rollback-all step has higher impact
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else if (getAvailableRollback(failedPackage) != null) {
+ // Rollback is available, we may get a callback into #execute
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else if (anyRollbackAvailable) {
+ // If any rollbacks are available, we will commit them
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
+ }
}
return impact;
@@ -133,16 +157,34 @@
@Override
public boolean execute(@Nullable VersionedPackage failedPackage,
@FailureReasons int rollbackReason, int mitigationCount) {
- if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
- mHandler.post(() -> rollbackAll(rollbackReason));
- return true;
- }
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+ if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
+ return true;
+ }
- RollbackInfo rollback = getAvailableRollback(failedPackage);
- if (rollback != null) {
- mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
+ List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
+ availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackInfo rollback = getRollbackForPackage(failedPackage, lowImpactRollbacks);
+ if (rollback != null) {
+ mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
+ } else if (!lowImpactRollbacks.isEmpty()) {
+ // Apply all available low impact rollbacks.
+ mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
+ }
} else {
- mHandler.post(() -> rollbackAll(rollbackReason));
+ if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ mHandler.post(() -> rollbackAll(rollbackReason));
+ return true;
+ }
+
+ RollbackInfo rollback = getAvailableRollback(failedPackage);
+ if (rollback != null) {
+ mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
+ } else {
+ mHandler.post(() -> rollbackAll(rollbackReason));
+ }
}
// Assume rollbacks executed successfully
@@ -150,6 +192,31 @@
}
@Override
+ public int onBootLoop(int mitigationCount) {
+ int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+ if (!availableRollbacks.isEmpty()) {
+ impact = getUserImpactBasedOnRollbackImpactLevel(availableRollbacks);
+ }
+ }
+ return impact;
+ }
+
+ @Override
+ public boolean executeBootLoopMitigation(int mitigationCount) {
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+
+ triggerLeastImpactLevelRollback(availableRollbacks,
+ PackageWatchdog.FAILURE_REASON_BOOT_LOOP);
+ return true;
+ }
+ return false;
+ }
+
+
+ @Override
public String getName() {
return NAME;
}
@@ -161,13 +228,16 @@
@Override
public boolean mayObservePackage(String packageName) {
- if (mContext.getSystemService(RollbackManager.class)
- .getAvailableRollbacks().isEmpty()) {
+ if (getAvailableRollbacks().isEmpty()) {
return false;
}
return isPersistentSystemApp(packageName);
}
+ private List<RollbackInfo> getAvailableRollbacks() {
+ return mContext.getSystemService(RollbackManager.class).getAvailableRollbacks();
+ }
+
private boolean isPersistentSystemApp(@NonNull String packageName) {
PackageManager pm = mContext.getPackageManager();
try {
@@ -272,6 +342,40 @@
return null;
}
+ @AnyThread
+ private RollbackInfo getRollbackForPackage(@Nullable VersionedPackage failedPackage,
+ List<RollbackInfo> availableRollbacks) {
+ if (failedPackage == null) {
+ return null;
+ }
+
+ for (RollbackInfo rollback : availableRollbacks) {
+ for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
+ if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) {
+ return rollback;
+ }
+ // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have
+ // to rely on complicated reasoning as below
+
+ // Due to b/147666157, for apk in apex, we do not know the version we are rolling
+ // back from. But if a package X is embedded in apex A exclusively (not embedded in
+ // any other apex), which is not guaranteed, then it is sufficient to check only
+ // package names here, as the version of failedPackage and the PackageRollbackInfo
+ // can't be different. If failedPackage has a higher version, then it must have
+ // been updated somehow. There are two ways: it was updated by an update of apex A
+ // or updated directly as apk. In both cases, this rollback would have gotten
+ // expired when onPackageReplaced() was called. Since the rollback exists, it has
+ // same version as failedPackage.
+ if (packageRollback.isApkInApex()
+ && packageRollback.getVersionRolledBackFrom().getPackageName()
+ .equals(failedPackage.getPackageName())) {
+ return rollback;
+ }
+ }
+ }
+ return null;
+ }
+
/**
* Returns {@code true} if staged session associated with {@code rollbackId} was marked
* as handled, {@code false} if already handled.
@@ -396,12 +500,6 @@
@FailureReasons int rollbackReason) {
assertInWorkerThread();
- if (isAutomaticRollbackDenied(SystemConfig.getInstance(), failedPackage)) {
- Slog.d(TAG, "Automatic rollback not allowed for package "
- + failedPackage.getPackageName());
- return;
- }
-
final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
int reasonToLog = WatchdogRollbackLogger.mapFailureReasonToMetric(rollbackReason);
final String failedPackageToLog;
@@ -465,17 +563,6 @@
}
/**
- * Returns true if this package is not eligible for automatic rollback.
- */
- @VisibleForTesting
- @AnyThread
- public static boolean isAutomaticRollbackDenied(SystemConfig systemConfig,
- VersionedPackage versionedPackage) {
- return systemConfig.getAutomaticRollbackDenylistedPackages()
- .contains(versionedPackage.getPackageName());
- }
-
- /**
* Two-phase rollback:
* 1. roll back rebootless apexes first
* 2. roll back all remaining rollbacks if native crash doesn't stop after (1) is done
@@ -495,14 +582,62 @@
boolean found = false;
for (RollbackInfo rollback : rollbacks) {
if (isRebootlessApex(rollback)) {
- VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
- rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
+ VersionedPackage firstRollback =
+ rollback.getPackages().get(0).getVersionRolledBackFrom();
+ rollbackPackage(rollback, firstRollback,
+ PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
found = true;
}
}
return found;
}
+ /**
+ * Rollback the package that has minimum rollback impact level.
+ * @param availableRollbacks all available rollbacks
+ * @param rollbackReason reason to rollback
+ */
+ private void triggerLeastImpactLevelRollback(List<RollbackInfo> availableRollbacks,
+ @FailureReasons int rollbackReason) {
+ int minRollbackImpactLevel = getMinRollbackImpactLevel(availableRollbacks);
+
+ if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_LOW) {
+ // Apply all available low impact rollbacks.
+ mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
+ } else if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH) {
+ // Rollback one package at a time. If that doesn't resolve the issue, rollback
+ // next with same impact level.
+ mHandler.post(() -> rollbackHighImpact(availableRollbacks, rollbackReason));
+ }
+ }
+
+ /**
+ * sort the available high impact rollbacks by first package name to have a deterministic order.
+ * Apply the first available rollback.
+ * @param availableRollbacks all available rollbacks
+ * @param rollbackReason reason to rollback
+ */
+ @WorkerThread
+ private void rollbackHighImpact(List<RollbackInfo> availableRollbacks,
+ @FailureReasons int rollbackReason) {
+ assertInWorkerThread();
+ List<RollbackInfo> highImpactRollbacks =
+ getRollbacksAvailableForImpactLevel(
+ availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+
+ // sort rollbacks based on package name of the first package. This is to have a
+ // deterministic order of rollbacks.
+ List<RollbackInfo> sortedHighImpactRollbacks = highImpactRollbacks.stream().sorted(
+ Comparator.comparing(a -> a.getPackages().get(0).getPackageName())).toList();
+ VersionedPackage firstRollback =
+ sortedHighImpactRollbacks
+ .get(0)
+ .getPackages()
+ .get(0)
+ .getVersionRolledBackFrom();
+ rollbackPackage(sortedHighImpactRollbacks.get(0), firstRollback, rollbackReason);
+ }
+
@WorkerThread
private void rollbackAll(@FailureReasons int rollbackReason) {
assertInWorkerThread();
@@ -522,8 +657,77 @@
}
for (RollbackInfo rollback : rollbacks) {
- VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
- rollbackPackage(rollback, sample, rollbackReason);
+ VersionedPackage firstRollback =
+ rollback.getPackages().get(0).getVersionRolledBackFrom();
+ rollbackPackage(rollback, firstRollback, rollbackReason);
}
}
+
+ /**
+ * Rollback all available low impact rollbacks
+ * @param availableRollbacks all available rollbacks
+ * @param rollbackReason reason to rollbacks
+ */
+ @WorkerThread
+ private void rollbackAllLowImpact(
+ List<RollbackInfo> availableRollbacks, @FailureReasons int rollbackReason) {
+ assertInWorkerThread();
+
+ List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
+ availableRollbacks,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ if (useTwoPhaseRollback(lowImpactRollbacks)) {
+ return;
+ }
+
+ Slog.i(TAG, "Rolling back all available low impact rollbacks");
+ // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
+ // pending staged rollbacks are handled.
+ for (RollbackInfo rollback : lowImpactRollbacks) {
+ if (rollback.isStaged()) {
+ mPendingStagedRollbackIds.add(rollback.getRollbackId());
+ }
+ }
+
+ for (RollbackInfo rollback : lowImpactRollbacks) {
+ VersionedPackage firstRollback =
+ rollback.getPackages().get(0).getVersionRolledBackFrom();
+ rollbackPackage(rollback, firstRollback, rollbackReason);
+ }
+ }
+
+ private List<RollbackInfo> getRollbacksAvailableForImpactLevel(
+ List<RollbackInfo> availableRollbacks, int impactLevel) {
+ return availableRollbacks.stream()
+ .filter(rollbackInfo -> rollbackInfo.getRollbackImpactLevel() == impactLevel)
+ .toList();
+ }
+
+ private int getMinRollbackImpactLevel(List<RollbackInfo> availableRollbacks) {
+ return availableRollbacks.stream()
+ .mapToInt(RollbackInfo::getRollbackImpactLevel)
+ .min()
+ .orElse(-1);
+ }
+
+ private int getUserImpactBasedOnRollbackImpactLevel(List<RollbackInfo> availableRollbacks) {
+ int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ int minImpact = getMinRollbackImpactLevel(availableRollbacks);
+ switch (minImpact) {
+ case PackageManager.ROLLBACK_USER_IMPACT_LOW:
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
+ break;
+ case PackageManager.ROLLBACK_USER_IMPACT_HIGH:
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_90;
+ break;
+ default:
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ }
+ return impact;
+ }
+
+ @VisibleForTesting
+ Handler getHandler() {
+ return mHandler;
+ }
}
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
index 898c543..519c0ed 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -18,6 +18,7 @@
import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING;
import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
@@ -258,6 +259,8 @@
return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
case PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING:
return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
+ case PackageWatchdog.FAILURE_REASON_BOOT_LOOP:
+ return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING;
default:
return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index b04c7c5..31db840 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -326,7 +326,6 @@
private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>();
private final ArraySet<String> mRollbackWhitelistedPackages = new ArraySet<>();
- private final ArraySet<String> mAutomaticRollbackDenylistedPackages = new ArraySet<>();
private final ArraySet<String> mWhitelistedStagedInstallers = 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.
@@ -475,10 +474,6 @@
return mRollbackWhitelistedPackages;
}
- public Set<String> getAutomaticRollbackDenylistedPackages() {
- return mAutomaticRollbackDenylistedPackages;
- }
-
public Set<String> getWhitelistedStagedInstallers() {
return mWhitelistedStagedInstallers;
}
@@ -1396,16 +1391,6 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
- case "automatic-rollback-denylisted-app": {
- String pkgname = parser.getAttributeValue(null, "package");
- if (pkgname == null) {
- Slog.w(TAG, "<" + name + "> without package in " + permFile
- + " at " + parser.getPositionDescription());
- } else {
- mAutomaticRollbackDenylistedPackages.add(pkgname);
- }
- XmlUtils.skipCurrentTag(parser);
- } break;
case "whitelisted-staged-installer": {
if (allowAppConfigs) {
String pkgname = parser.getAttributeValue(null, "package");
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index d96fc33..e5cdf45 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -1212,13 +1212,20 @@
rollback.makeAvailable();
mPackageHealthObserver.notifyRollbackAvailable(rollback.info);
- // TODO(zezeozue): Provide API to explicitly start observing instead
- // of doing this for all rollbacks. If we do this for all rollbacks,
- // should document in PackageInstaller.SessionParams#setEnableRollback
- // After enabling and committing any rollback, observe packages and
- // prepare to rollback if packages crashes too frequently.
- mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
- mRollbackLifetimeDurationInMillis);
+ if (Flags.recoverabilityDetection()) {
+ if (rollback.info.getRollbackImpactLevel() == PackageManager.ROLLBACK_USER_IMPACT_LOW) {
+ // TODO(zezeozue): Provide API to explicitly start observing instead
+ // of doing this for all rollbacks. If we do this for all rollbacks,
+ // should document in PackageInstaller.SessionParams#setEnableRollback
+ // After enabling and committing any rollback, observe packages and
+ // prepare to rollback if packages crashes too frequently.
+ mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
+ mRollbackLifetimeDurationInMillis);
+ }
+ } else {
+ mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
+ mRollbackLifetimeDurationInMillis);
+ }
runExpiration();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
index a140730..d6e246f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
@@ -23,7 +23,13 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -33,14 +39,17 @@
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
-import android.util.Log;
-import android.util.Xml;
+import android.crashrecovery.flags.Flags;
+import android.os.Handler;
+import android.os.MessageQueue;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.PackageWatchdog;
import com.android.server.SystemConfig;
+import com.android.server.pm.ApexManager;
import org.junit.After;
import org.junit.Before;
@@ -49,18 +58,16 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
-import org.xmlpull.v1.XmlPullParser;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
+import java.time.Duration;
import java.util.List;
-import java.util.Scanner;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
@@ -78,10 +85,18 @@
@Mock
PackageManager mMockPackageManager;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private ApexManager mApexManager;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private MockitoSession mSession;
private static final String APP_A = "com.package.a";
private static final String APP_B = "com.package.b";
+ private static final String APP_C = "com.package.c";
private static final long VERSION_CODE = 1L;
+ private static final long VERSION_CODE_2 = 2L;
private static final String LOG_TAG = "RollbackPackageHealthObserverTest";
private SystemConfig mSysConfig;
@@ -101,7 +116,6 @@
// Mock PackageWatchdog
doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
.when(() -> PackageWatchdog.getInstance(mMockContext));
-
}
@After
@@ -121,7 +135,7 @@
@Test
public void testHealthCheckLevels() {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext));
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
VersionedPackage testFailedPackage = new VersionedPackage(APP_A, VERSION_CODE);
VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
@@ -165,14 +179,14 @@
@Test
public void testIsPersistent() {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext));
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
assertTrue(observer.isPersistent());
}
@Test
public void testMayObservePackage_withoutAnyRollback() {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext));
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of());
assertFalse(observer.mayObservePackage(APP_A));
@@ -182,7 +196,7 @@
public void testMayObservePackage_forPersistentApp()
throws PackageManager.NameNotFoundException {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext));
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
ApplicationInfo info = new ApplicationInfo();
info.flags = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM;
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -197,7 +211,7 @@
public void testMayObservePackage_forNonPersistentApp()
throws PackageManager.NameNotFoundException {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext));
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo));
when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo));
@@ -208,96 +222,720 @@
}
/**
- * Test that isAutomaticRollbackDenied works correctly when packages that are not
- * denied are sent.
+ * Test that when impactLevel is low returns user impact level 70
*/
@Test
- public void isRollbackAllowedTest_false() throws IOException {
- final String contents =
- "<config>\n"
- + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
- + "</config>";
- final File folder = createTempSubfolder("folder");
- createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+ public void healthCheckFailed_impactLevelLow_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
- readPermissions(folder, /* Grant all permission flags */ ~0);
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
- new VersionedPackage("com.test.package", 1))).isEqualTo(false);
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ observer.onHealthCheckFailed(secondFailedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
}
/**
- * Test that isAutomaticRollbackDenied works correctly when packages that are
- * denied are sent.
+ * HealthCheckFailed should only return low impact rollbacks. High impact rollbacks are only
+ * for bootloop.
*/
@Test
- public void isRollbackAllowedTest_true() throws IOException {
- final String contents =
- "<config>\n"
- + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
- + "</config>";
- final File folder = createTempSubfolder("folder");
- createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+ public void healthCheckFailed_impactLevelHigh_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
- readPermissions(folder, /* Grant all permission flags */ ~0);
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
- new VersionedPackage("com.android.vending", 1))).isEqualTo(true);
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+ observer.onHealthCheckFailed(secondFailedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
}
/**
- * Test that isAutomaticRollbackDenied works correctly when no config is present
+ * When the rollback impact level is manual only return user impact level 0. (User impact level
+ * 0 is ignored by package watchdog)
*/
@Test
- public void isRollbackAllowedTest_noConfig() throws IOException {
- final File folder = createTempSubfolder("folder");
+ public void healthCheckFailed_impactLevelManualOnly_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
- readPermissions(folder, /* Grant all permission flags */ ~0);
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
- new VersionedPackage("com.android.vending", 1))).isEqualTo(false);
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+ observer.onHealthCheckFailed(secondFailedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
}
/**
- * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
- *
- * @param folder pre-existing subdirectory of mTemporaryFolder to put the file
- * @param fileName name of the file (e.g. filename.xml) to create
- * @param contents contents to write to the file
- * @return the newly created file
+ * When both low impact and high impact are present, return 70.
*/
- private File createTempFile(File folder, String fileName, String contents)
- throws IOException {
- File file = new File(folder, fileName);
- BufferedWriter bw = new BufferedWriter(new FileWriter(file));
- bw.write(contents);
- bw.close();
+ @Test
+ public void healthCheckFailed_impactLevelLowAndHigh_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
- // Print to logcat for test debugging.
- Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath());
- Scanner input = new Scanner(file);
- while (input.hasNextLine()) {
- Log.d(LOG_TAG, input.nextLine());
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ observer.onHealthCheckFailed(failedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
+ }
+
+ /**
+ * When low impact rollback is available roll it back.
+ */
+ @Test
+ public void execute_impactLevelLow_nativeCrash_rollback()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(secondFailedPackage,
+ PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager).getAvailableRollbacks();
+ verify(mRollbackManager).commitRollback(eq(rollbackId), any(), any());
+ }
+
+ /**
+ * Rollback the failing package if rollback is available for it
+ */
+ @Test
+ public void execute_impactLevelLow_rollbackFailedPackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(appBFrom, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager).commitRollback(argument.capture(), any(), any());
+ // Rollback package App B as the failing package is B
+ assertThat(argument.getValue()).isEqualTo(rollbackId2);
+ }
+
+ /**
+ * Rollback all available rollbacks if the rollback is not available for failing package.
+ */
+ @Test
+ public void execute_impactLevelLow_rollbackAll()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(2)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback A and B when the failing package doesn't have a rollback
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1, rollbackId2));
+ }
+
+ /**
+ * rollback low impact package if both low and high impact packages are available
+ */
+ @Test
+ public void execute_impactLevelLowAndHigh_rollbackLow()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(1)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback A and B when the failing package doesn't have a rollback
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1));
+ }
+
+ /**
+ * Don't roll back high impact package if only high impact package is available. high impact
+ * rollback to be rolled back only on bootloop.
+ */
+ @Test
+ public void execute_impactLevelHigh_rollbackHigh()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, never()).commitRollback(argument.capture(), any(), any());
+
+ }
+
+ /**
+ * Test that when impactLevel is low returns user impact level 70
+ */
+ @Test
+ public void onBootLoop_impactLevelLow_onePackage() throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ observer.onBootLoop(1));
+ }
+
+ @Test
+ public void onBootLoop_impactLevelHigh_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_90,
+ observer.onBootLoop(1));
+ }
+
+ /**
+ * When the rollback impact level is manual only return user impact level 0. (User impact level
+ * 0 is ignored by package watchdog)
+ */
+ @Test
+ public void onBootLoop_impactLevelManualOnly_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+ observer.onBootLoop(1));
+ }
+
+ /**
+ * When both low impact and high impact are present, return 70.
+ */
+ @Test
+ public void onBootLoop_impactLevelLowAndHigh_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ observer.onBootLoop(1));
+ }
+
+ /**
+ * Rollback all available rollbacks if the rollback is not available for failing package.
+ */
+ @Test
+ public void executeBootLoopMitigation_impactLevelLow_rollbackAll()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.executeBootLoopMitigation(1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(2)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback A and B when the failing package doesn't have a rollback
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1, rollbackId2));
+ }
+
+ /**
+ * rollback low impact package if both low and high impact packages are available
+ */
+ @Test
+ public void executeBootLoopMitigation_impactLevelLowAndHigh_rollbackLow()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.executeBootLoopMitigation(1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(1)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback A and B when the failing package doesn't have a rollback
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1));
+ }
+
+ /**
+ * Rollback high impact package if only high impact package is available
+ */
+ @Test
+ public void executeBootLoopMitigation_impactLevelHigh_rollbackHigh()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.executeBootLoopMitigation(1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(1)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback high impact packages when no other rollback available
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId2));
+ }
+
+ /**
+ * Rollback only low impact available rollbacks if both low and manual only are available.
+ */
+ @Test
+ public void execute_impactLevelLowAndManual_rollbackLowImpactOnly()
+ throws PackageManager.NameNotFoundException, InterruptedException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(1)).commitRollback(
+ argument.capture(), any(), any());
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1));
+ }
+
+ /**
+ * Do not roll back if only manual rollback is available.
+ */
+ @Test
+ public void execute_impactLevelManual_rollbackLowImpactOnly()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, never()).commitRollback(argument.capture(), any(), any());
+ }
+
+ /**
+ * Rollback alphabetically first package if multiple high impact rollbacks are available.
+ */
+ @Test
+ public void executeBootLoopMitigation_impactLevelHighMultiplePackage_rollbackHigh()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoB),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ int rollbackId2 = 2;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.executeBootLoopMitigation(1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(1)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback APP_A because it is first alphabetically
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId2));
+ }
+
+ private void waitForIdleHandler(Handler handler, Duration timeout) {
+ final MessageQueue queue = handler.getLooper().getQueue();
+ final CountDownLatch latch = new CountDownLatch(1);
+ queue.addIdleHandler(() -> {
+ latch.countDown();
+ // Remove idle handler
+ return false;
+ });
+ try {
+ latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail("Interrupted unexpectedly: " + e);
}
-
- return file;
- }
-
- private void readPermissions(File libraryDir, int permissionFlag) {
- final XmlPullParser parser = Xml.newPullParser();
- mSysConfig.readPermissions(parser, libraryDir, permissionFlag);
- }
-
- /**
- * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
- *
- * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
- * @return the folder
- */
- private File createTempSubfolder(String folderName)
- throws IOException {
- File folder = new File(mTemporaryFolder.getRoot(), folderName);
- folder.mkdirs();
- return folder;
}
}
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 aca96ad..d073f5b 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -595,56 +595,6 @@
}
/**
- * Test that getRollbackDenylistedPackages works correctly for the tag:
- * {@code automatic-rollback-denylisted-app}.
- */
- @Test
- public void automaticRollbackDeny_vending() throws IOException {
- final String contents =
- "<config>\n"
- + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
- + "</config>";
- final File folder = createTempSubfolder("folder");
- createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
-
- readPermissions(folder, /* Grant all permission flags */ ~0);
-
- assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages())
- .containsExactly("com.android.vending");
- }
-
- /**
- * Test that getRollbackDenylistedPackages works correctly for the tag:
- * {@code automatic-rollback-denylisted-app} without any packages.
- */
- @Test
- public void automaticRollbackDeny_empty() throws IOException {
- final String contents =
- "<config>\n"
- + " <automatic-rollback-denylisted-app />\n"
- + "</config>";
- final File folder = createTempSubfolder("folder");
- createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
-
- readPermissions(folder, /* Grant all permission flags */ ~0);
-
- assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()).isEmpty();
- }
-
- /**
- * Test that getRollbackDenylistedPackages works correctly for the tag:
- * {@code automatic-rollback-denylisted-app} without the corresponding config.
- */
- @Test
- public void automaticRollbackDeny_noConfig() throws IOException {
- final File folder = createTempSubfolder("folder");
-
- readPermissions(folder, /* Grant all permission flags */ ~0);
-
- assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()).isEmpty();
- }
-
- /**
* Tests that readPermissions works correctly for the tag: {@code update-ownership}.
*/
@Test