From cae9b76ea12aa489c6a59b60e303185207d4ae47 Mon Sep 17 00:00:00 2001 From: Shrinidhi Hegde <shrinidhihegde@google.com> Date: Tue, 6 Feb 2024 15:50:18 +0000 Subject: [PATCH] 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 --- .../com/android/server/PackageWatchdog.java | 8 +- .../RollbackPackageHealthObserver.java | 300 +++++-- .../rollback/WatchdogRollbackLogger.java | 3 + .../java/com/android/server/SystemConfig.java | 15 - .../rollback/RollbackManagerServiceImpl.java | 21 +- .../RollbackPackageHealthObserverTest.java | 798 ++++++++++++++++-- .../server/systemconfig/SystemConfigTest.java | 50 -- 7 files changed, 993 insertions(+), 202 deletions(-) diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java index b5cf011c32a6..ce8fb6568bd5 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 class PackageWatchdog { 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 @@ public class PackageWatchdog { 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 @@ public class PackageWatchdog { 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 @@ public class PackageWatchdog { 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 dd74a2a978b2..5fb47dd9b95a 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.pm.VersionedPackage; 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; 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.FileReader; 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 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { // 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 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { 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 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { } } + 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 (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 (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; + } } return impact; @@ -133,22 +157,65 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { @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 return true; } + @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 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { @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 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { 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 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { @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; @@ -464,17 +562,6 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { Collections.singletonList(failedPackage), rollbackReceiver.getIntentSender()); } - /** - * 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 @@ -495,14 +582,62 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { 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 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { } 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 898c5439a293..519c0edfc532 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 @@ package com.android.server.rollback; 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 @@ public final class WatchdogRollbackLogger { 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 b04c7c52efbd..31db840dac00 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -326,7 +326,6 @@ public class SystemConfig { 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 @@ public class SystemConfig { return mRollbackWhitelistedPackages; } - public Set<String> getAutomaticRollbackDenylistedPackages() { - return mAutomaticRollbackDenylistedPackages; - } - public Set<String> getWhitelistedStagedInstallers() { return mWhitelistedStagedInstallers; } @@ -1396,16 +1391,6 @@ public class SystemConfig { } 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 d96fc332ce5a..e5cdf4577408 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 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba 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 a14073006c31..d6e246fc7ee1 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 com.google.common.truth.Truth.assertThat; 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.pm.VersionedPackage; 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.Test; 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 @@ public class RollbackPackageHealthObserverTest { @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 @@ public class RollbackPackageHealthObserverTest { // Mock PackageWatchdog doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog) .when(() -> PackageWatchdog.getInstance(mMockContext)); - } @After @@ -121,7 +135,7 @@ public class RollbackPackageHealthObserverTest { @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 @@ public class RollbackPackageHealthObserverTest { @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 class RollbackPackageHealthObserverTest { 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 class RollbackPackageHealthObserverTest { 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 @@ public class RollbackPackageHealthObserverTest { } /** - * 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); - - readPermissions(folder, /* Grant all permission flags */ ~0); - - assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig, - new VersionedPackage("com.test.package", 1))).isEqualTo(false); + 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); + + 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.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); - - readPermissions(folder, /* Grant all permission flags */ ~0); - - assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig, - new VersionedPackage("com.android.vending", 1))).isEqualTo(true); + 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); + + 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.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(); - - // 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()); - } + @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); + + 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); - return file; + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_90, + observer.onBootLoop(1)); } - private void readPermissions(File libraryDir, int permissionFlag) { - final XmlPullParser parser = Xml.newPullParser(); - mSysConfig.readPermissions(parser, libraryDir, permissionFlag); + /** + * 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)); } /** - * 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 + * When both low impact and high impact are present, return 70. */ - private File createTempSubfolder(String folderName) - throws IOException { - File folder = new File(mTemporaryFolder.getRoot(), folderName); - folder.mkdirs(); - return folder; + @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); + } } } 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 aca96ad20385..d073f5bfebe4 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -594,56 +594,6 @@ public class SystemConfigTest { assertFooIsOnlySharedLibrary(); } - /** - * 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}. */ -- GitLab