Merge "SystemUI as an exception to user impact level limit" into main
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index eb03709..d9926a4 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -150,6 +150,15 @@
     private static final int DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
             PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
 
+    // Comma separated list of all packages exempt from user impact level threshold. If a package
+    // in the list is crash looping, all the mitigations including factory reset will be performed.
+    private static final String PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD =
+            "persist.device_config.configuration.packages_exempt_from_impact_level_threshold";
+
+    // Comma separated list of default packages exempt from user impact level threshold.
+    private static final String DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD =
+            "com.android.systemui";
+
     private long mNumberOfNativeCrashPollsRemaining;
 
     private static final int DB_VERSION = 1;
@@ -196,6 +205,8 @@
     private final DeviceConfig.OnPropertiesChangedListener
             mOnPropertyChangedListener = this::onPropertyChanged;
 
+    private final Set<String> mPackagesExemptFromImpactLevelThreshold = new ArraySet<>();
+
     // The set of packages that have been synced with the ExplicitHealthCheckController
     @GuardedBy("mLock")
     private Set<String> mRequestedHealthCheckPackages = new ArraySet<>();
@@ -518,7 +529,7 @@
                               @FailureReasons int failureReason,
                               int currentObserverImpact,
                               int mitigationCount) {
-        if (currentObserverImpact < getUserImpactLevelLimit()) {
+        if (allowMitigations(currentObserverImpact, versionedPackage)) {
             synchronized (mLock) {
                 mLastMitigation = mSystemClock.uptimeMillis();
             }
@@ -526,6 +537,13 @@
         }
     }
 
+    private boolean allowMitigations(int currentObserverImpact,
+            VersionedPackage versionedPackage) {
+        return currentObserverImpact < getUserImpactLevelLimit()
+                || getPackagesExemptFromImpactLevelThreshold().contains(
+                versionedPackage.getPackageName());
+    }
+
     private long getMitigationWindowMs() {
         return SystemProperties.getLong(MITIGATION_WINDOW_MS, DEFAULT_MITIGATION_WINDOW_MS);
     }
@@ -657,6 +675,15 @@
                 DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD);
     }
 
+    private Set<String> getPackagesExemptFromImpactLevelThreshold() {
+        if (mPackagesExemptFromImpactLevelThreshold.isEmpty()) {
+            String packageNames = SystemProperties.get(PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD,
+                    DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD);
+            return Set.of(packageNames.split("\\s*,\\s*"));
+        }
+        return mPackagesExemptFromImpactLevelThreshold;
+    }
+
     /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
     @Retention(SOURCE)
     @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
index 3722fef..c0e90f9 100644
--- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
@@ -35,6 +35,7 @@
 
 import android.Manifest;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
@@ -77,6 +78,7 @@
 import java.io.File;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -413,6 +415,311 @@
         verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
     }
 
+    @Test
+    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    public void testCrashLoopWithRescuePartyAndRollbackObserver() throws Exception {
+        PackageWatchdog watchdog = createWatchdog();
+        RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
+        RollbackPackageHealthObserver rollbackObserver =
+                setUpRollbackPackageHealthObserver(watchdog);
+        VersionedPackage versionedPackageA = new VersionedPackage(APP_A, VERSION_CODE);
+
+        when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).then(inv -> {
+            ApplicationInfo info = new ApplicationInfo();
+            info.flags |= ApplicationInfo.FLAG_PERSISTENT
+                    | ApplicationInfo.FLAG_SYSTEM;
+            return info;
+        });
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: SCOPED_DEVICE_CONFIG_RESET
+        verify(rescuePartyObserver).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rollbackObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: ALL_DEVICE_CONFIG_RESET
+        verify(rescuePartyObserver).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
+        verify(rollbackObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: WARM_REBOOT
+        verify(rescuePartyObserver).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+        verify(rollbackObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: Low impact rollback
+        verify(rollbackObserver).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+
+        // update available rollbacks to mock rollbacks being applied after the call to
+        // rollbackObserver.execute
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+                List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD reached. No more mitigations applied
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+        verify(rollbackObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    public void testCrashLoopWithRescuePartyAndRollbackObserverEnableDeprecateFlagReset()
+            throws Exception {
+        PackageWatchdog watchdog = createWatchdog();
+        RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
+        RollbackPackageHealthObserver rollbackObserver =
+                setUpRollbackPackageHealthObserver(watchdog);
+        VersionedPackage versionedPackageA = new VersionedPackage(APP_A, VERSION_CODE);
+
+        when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).then(inv -> {
+            ApplicationInfo info = new ApplicationInfo();
+            info.flags |= ApplicationInfo.FLAG_PERSISTENT
+                    | ApplicationInfo.FLAG_SYSTEM;
+            return info;
+        });
+
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: WARM_REBOOT
+        verify(rescuePartyObserver).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rollbackObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: Low impact rollback
+        verify(rollbackObserver).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+        // update available rollbacks to mock rollbacks being applied after the call to
+        // rollbackObserver.execute
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+                List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD reached. No more mitigations applied
+        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rollbackObserver, never()).execute(versionedPackageA,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    public void testCrashLoopSystemUIWithRescuePartyAndRollbackObserver() throws Exception {
+        PackageWatchdog watchdog = createWatchdog();
+        RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
+        RollbackPackageHealthObserver rollbackObserver =
+                setUpRollbackPackageHealthObserver(watchdog);
+        String systemUi = "com.android.systemui";
+        VersionedPackage versionedPackageUi = new VersionedPackage(
+                systemUi, VERSION_CODE);
+        RollbackInfo rollbackInfoUi = getRollbackInfo(systemUi, VERSION_CODE, 1,
+                PackageManager.ROLLBACK_USER_IMPACT_LOW);
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_LOW,
+                ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL, rollbackInfoUi));
+
+        when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).then(inv -> {
+            ApplicationInfo info = new ApplicationInfo();
+            info.flags |= ApplicationInfo.FLAG_PERSISTENT
+                    | ApplicationInfo.FLAG_SYSTEM;
+            return info;
+        });
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: SCOPED_DEVICE_CONFIG_RESET
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: ALL_DEVICE_CONFIG_RESET
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: WARM_REBOOT
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: Low impact rollback
+        verify(rollbackObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+        // update available rollbacks to mock rollbacks being applied after the call to
+        // rollbackObserver.execute
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+                List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: RESET_SETTINGS_UNTRUSTED_DEFAULTS
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 5);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: RESET_SETTINGS_UNTRUSTED_CHANGES
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 5);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 6);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: RESET_SETTINGS_TRUSTED_DEFAULTS
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 6);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 7);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: Factory reset. High impact rollbacks are performed only for boot loops.
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 7);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 8);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+    public void testCrashLoopSystemUIWithRescuePartyAndRollbackObserverEnableDeprecateFlagReset()
+            throws Exception {
+        PackageWatchdog watchdog = createWatchdog();
+        RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
+        RollbackPackageHealthObserver rollbackObserver =
+                setUpRollbackPackageHealthObserver(watchdog);
+        String systemUi = "com.android.systemui";
+        VersionedPackage versionedPackageUi = new VersionedPackage(
+                systemUi, VERSION_CODE);
+        RollbackInfo rollbackInfoUi = getRollbackInfo(systemUi, VERSION_CODE, 1,
+                PackageManager.ROLLBACK_USER_IMPACT_LOW);
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_LOW,
+                ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL, rollbackInfoUi));
+
+        when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).then(inv -> {
+            ApplicationInfo info = new ApplicationInfo();
+            info.flags |= ApplicationInfo.FLAG_PERSISTENT
+                    | ApplicationInfo.FLAG_SYSTEM;
+            return info;
+        });
+
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: WARM_REBOOT
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: Low impact rollback
+        verify(rollbackObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+        // update available rollbacks to mock rollbacks being applied after the call to
+        // rollbackObserver.execute
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+                List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
+
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+        // Mitigation: Factory reset. High impact rollbacks are performed only for boot loops.
+        verify(rescuePartyObserver).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
+        verify(rollbackObserver, never()).execute(versionedPackageUi,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+    }
+
     RollbackPackageHealthObserver setUpRollbackPackageHealthObserver(PackageWatchdog watchdog) {
         RollbackPackageHealthObserver rollbackObserver =
                 spy(new RollbackPackageHealthObserver(mSpyContext, mApexManager));
@@ -424,7 +731,6 @@
         watchdog.registerHealthObserver(rollbackObserver);
         return rollbackObserver;
     }
-
     RescuePartyObserver setUpRescuePartyObserver(PackageWatchdog watchdog) {
         setCrashRecoveryPropRescueBootCount(0);
         RescuePartyObserver rescuePartyObserver = spy(RescuePartyObserver.getInstance(mSpyContext));
@@ -686,4 +992,20 @@
         mTestLooper.moveTimeForward(milliSeconds);
         mTestLooper.dispatchAll();
     }
+
+    private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog,
+            List<VersionedPackage> packages, int failureReason) {
+        long triggerFailureCount = watchdog.getTriggerFailureCount();
+        if (failureReason == PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK
+                || failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+            triggerFailureCount = 1;
+        }
+        for (int i = 0; i < triggerFailureCount; i++) {
+            watchdog.onPackageFailure(packages, failureReason);
+        }
+        mTestLooper.dispatchAll();
+        if (Flags.recoverabilityDetection()) {
+            moveTimeForwardAndDispatch(watchdog.DEFAULT_MITIGATION_WINDOW_MS);
+        }
+    }
 }