Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f305f4de authored by Gavin Corkery's avatar Gavin Corkery
Browse files

Add package failure flags to Package Watchdog

This is a prerequisite for adding additional logging of
the Watchdog-triggered rollback reason. Add flags which
indicate the failure observed (native, crash, ANR, explicit
health check). These will be used in the future by
RollbackPackageHealthObserver to map the failure type to the
(new) set of available logging metrics.

Test: atest PackageWatchdogTest
Bug: 138782888
Change-Id: I7e7c5e5399011e2761dada2b989a95c2013307e9
parent feeeb653
Loading
Loading
Loading
Loading
+25 −6
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
@@ -81,6 +82,22 @@ public class PackageWatchdog {
    static final String PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED =
            "watchdog_explicit_health_check_enabled";

    public static final int FAILURE_REASON_UNKNOWN = 0;
    public static final int FAILURE_REASON_NATIVE_CRASH = 1;
    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;

    @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
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface FailureReasons {}

    // Duration to count package failures before it resets to 0
    @VisibleForTesting
    static final int DEFAULT_TRIGGER_FAILURE_DURATION_MS =
@@ -305,14 +322,15 @@ public class PackageWatchdog {
    }

    /**
     * Called when a process fails either due to a crash or ANR.
     * Called when a process fails due to a crash, ANR or explicit health check.
     *
     * <p>For each package contained in the process, one registered observer with the least user
     * impact will be notified for mitigation.
     *
     * <p>This method could be called frequently if there is a severe problem on the device.
     */
    public void onPackageFailure(List<VersionedPackage> packages) {
    public void onPackageFailure(List<VersionedPackage> packages,
            @FailureReasons int failureReason) {
        mLongTaskHandler.post(() -> {
            synchronized (mLock) {
                if (mAllObservers.isEmpty()) {
@@ -343,7 +361,7 @@ public class PackageWatchdog {

                    // Execute action with least user impact
                    if (currentObserverToNotify != null) {
                        currentObserverToNotify.execute(versionedPackage);
                        currentObserverToNotify.execute(versionedPackage, failureReason);
                    }
                }
            }
@@ -414,7 +432,7 @@ public class PackageWatchdog {
         *
         * @return {@code true} if action was executed successfully, {@code false} otherwise
         */
        boolean execute(VersionedPackage versionedPackage);
        boolean execute(VersionedPackage versionedPackage, @FailureReasons int failureReason);

        // TODO(b/120598832): Ensure uniqueness?
        /**
@@ -659,7 +677,8 @@ public class PackageWatchdog {
                    while (it.hasNext()) {
                        VersionedPackage versionedPkg = it.next().mPackage;
                        Slog.i(TAG, "Explicit health check failed for package " + versionedPkg);
                        registeredObserver.execute(versionedPkg);
                        registeredObserver.execute(versionedPkg,
                                PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
                    }
                }
            }
@@ -770,7 +789,7 @@ public class PackageWatchdog {
                    final List<VersionedPackage> pkgList = Collections.singletonList(pkg);
                    final long failureCount = getTriggerFailureCount();
                    for (int i = 0; i < failureCount; i++) {
                        onPackageFailure(pkgList);
                        onPackageFailure(pkgList, FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
                    }
                });
    }
+4 −2
Original line number Diff line number Diff line
@@ -446,7 +446,8 @@ class AppErrors {
                RescueParty.noteAppCrash(mContext, r.uid);
            }

            mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode());
            mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode(),
                    PackageWatchdog.FAILURE_REASON_APP_CRASH);
        }

        final int relaunchReason = r != null
@@ -900,7 +901,8 @@ class AppErrors {
        }
        // Notify PackageWatchdog without the lock held
        if (packageList != null) {
            mPackageWatchdog.onPackageFailure(packageList);
            mPackageWatchdog.onPackageFailure(packageList,
                    PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
        }
    }

+3 −2
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import android.util.StatsLog;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.server.PackageWatchdog;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;

@@ -106,7 +107,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
    }

    @Override
    public boolean execute(VersionedPackage failedPackage) {
    public boolean execute(VersionedPackage failedPackage, @FailureReasons int rollbackReason) {
        RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
        VersionedPackage moduleMetadataPackage = getModuleMetadataPackage();
        RollbackInfo rollback = getAvailableRollback(rollbackManager, failedPackage);
@@ -371,7 +372,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
        mNumberOfNativeCrashPollsRemaining--;
        // Check if native watchdog reported a crash
        if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
            execute(getModuleMetadataPackage());
            execute(getModuleMetadataPackage(), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
            // we stop polling after an attempt to execute rollback, regardless of whether the
            // attempt succeeds or not
        } else {
+100 −32
Original line number Diff line number Diff line
@@ -118,7 +118,8 @@ public class PackageWatchdogTest {

        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // The failed packages should be the same as the registered ones to ensure registration is
        // done successfully
@@ -135,7 +136,8 @@ public class PackageWatchdogTest {
        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
                        new VersionedPackage(APP_B, VERSION_CODE)));
                        new VersionedPackage(APP_B, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // The failed packages should be the same as the registered ones to ensure registration is
        // done successfully
@@ -151,7 +153,8 @@ public class PackageWatchdogTest {
        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
        watchdog.unregisterHealthObserver(observer);
        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // We should have no failed packages to ensure unregistration is done successfully
        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
@@ -167,7 +170,8 @@ public class PackageWatchdogTest {
        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
        watchdog.unregisterHealthObserver(observer2);
        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // observer1 should receive failed packages as intended.
        assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
@@ -183,7 +187,8 @@ public class PackageWatchdogTest {
        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
        moveTimeForwardAndDispatch(SHORT_DURATION);
        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // We should have no failed packages for the fatal failure is raised after expiration
        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
@@ -199,7 +204,8 @@ public class PackageWatchdogTest {
        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), LONG_DURATION);
        moveTimeForwardAndDispatch(SHORT_DURATION);
        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // We should have no failed packages for the fatal failure is raised after expiration
        assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
@@ -226,7 +232,8 @@ public class PackageWatchdogTest {
        moveTimeForwardAndDispatch((SHORT_DURATION / 2) + 1);

        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // Verify that we receive failed packages as expected for APP_A not expired
        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
@@ -252,7 +259,8 @@ public class PackageWatchdogTest {
        watchdog2.registerHealthObserver(observer2);
        raiseFatalFailureAndDispatch(watchdog2,
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
                        new VersionedPackage(APP_B, VERSION_CODE)));
                        new VersionedPackage(APP_B, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // We should receive failed packages as expected to ensure observers are persisted and
        // resumed correctly
@@ -274,7 +282,8 @@ public class PackageWatchdogTest {

        // Then fail APP_A below the threshold
        for (int i = 0; i < watchdog.getTriggerFailureCount() - 1; i++) {
            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                    PackageWatchdog.FAILURE_REASON_UNKNOWN);
        }

        // Run handler so package failures are dispatched to observers
@@ -301,7 +310,8 @@ public class PackageWatchdogTest {

        // Then fail APP_C (not observed) above the threshold
        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)));
                Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // Verify that observers are not notified
        assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
@@ -331,7 +341,8 @@ public class PackageWatchdogTest {

        // Then fail APP_A (different version) above the threshold
        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_A, differentVersionCode)));
                Arrays.asList(new VersionedPackage(APP_A, differentVersionCode)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // Verify that observers are not notified
        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
@@ -368,7 +379,8 @@ public class PackageWatchdogTest {
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
                        new VersionedPackage(APP_B, VERSION_CODE),
                        new VersionedPackage(APP_C, VERSION_CODE),
                        new VersionedPackage(APP_D, VERSION_CODE)));
                        new VersionedPackage(APP_D, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // Verify least impact observers are notifed of package failures
        List<String> observerNonePackages = observerNone.mMitigatedPackages;
@@ -411,7 +423,8 @@ public class PackageWatchdogTest {

        // Then fail APP_A above the threshold
        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // Verify only observerFirst is notifed
        assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
@@ -424,7 +437,8 @@ public class PackageWatchdogTest {

        // Then fail APP_A again above the threshold
        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // Verify only observerSecond is notifed cos it has least impact
        assertThat(observerSecond.mMitigatedPackages).containsExactly(APP_A);
@@ -437,7 +451,8 @@ public class PackageWatchdogTest {

        // Then fail APP_A again above the threshold
        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // Verify only observerFirst is notifed cos it has the only action
        assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
@@ -450,7 +465,8 @@ public class PackageWatchdogTest {

        // Then fail APP_A again above the threshold
        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // Verify no observer is notified cos no actions left
        assertThat(observerFirst.mMitigatedPackages).isEmpty();
@@ -474,7 +490,8 @@ public class PackageWatchdogTest {

        // Then fail APP_A above the threshold
        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // Verify only one observer is notifed
        assertThat(observer1.mMitigatedPackages).containsExactly(APP_A);
@@ -746,13 +763,15 @@ public class PackageWatchdogTest {
        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
        // Fail APP_A below the threshold which should not trigger package failures
        for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                    PackageWatchdog.FAILURE_REASON_UNKNOWN);
        }
        mTestLooper.dispatchAll();
        assertThat(observer.mHealthCheckFailedPackages).isEmpty();

        // One more to trigger the package failure
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);
        mTestLooper.dispatchAll();
        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
    }
@@ -773,20 +792,24 @@ public class PackageWatchdogTest {
        TestObserver observer = new TestObserver(OBSERVER_NAME_1);

        watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);
        mTestLooper.dispatchAll();
        moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);
        mTestLooper.dispatchAll();

        // We shouldn't receive APP_A since the interval of 2 failures is greater than
        // DEFAULT_TRIGGER_FAILURE_DURATION_MS.
        assertThat(observer.mHealthCheckFailedPackages).isEmpty();

        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)));
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);
        mTestLooper.dispatchAll();
        moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS - 1);
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)));
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);
        mTestLooper.dispatchAll();

        // We should receive APP_B since the interval of 2 failures is less than
@@ -809,7 +832,8 @@ public class PackageWatchdogTest {
        // small timeouts.
        moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS - 100);
        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // We should receive APP_A since the observer hasn't expired
        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
@@ -827,7 +851,8 @@ public class PackageWatchdogTest {
        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
        moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS + 1);
        raiseFatalFailureAndDispatch(watchdog,
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);

        // We should receive nothing since the observer has expired
        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
@@ -850,22 +875,59 @@ public class PackageWatchdogTest {

        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
        // Raise 2 failures at t=0 and t=900 respectively
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);
        mTestLooper.dispatchAll();
        moveTimeForwardAndDispatch(900);
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);
        mTestLooper.dispatchAll();

        // Raise 2 failures at t=1100
        moveTimeForwardAndDispatch(200);
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);
        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                PackageWatchdog.FAILURE_REASON_UNKNOWN);
        mTestLooper.dispatchAll();

        // We should receive APP_A since there are 3 failures within 1000ms window
        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
    }

    /** Test that observers execute correctly for different failure reasons */
    @Test
    public void testFailureReasons() {
        PackageWatchdog watchdog = createWatchdog();
        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
        TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
        TestObserver observer3 = new TestObserver(OBSERVER_NAME_3);
        TestObserver observer4 = new TestObserver(OBSERVER_NAME_4);

        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
        watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
        watchdog.startObservingHealth(observer3, Arrays.asList(APP_C), SHORT_DURATION);
        watchdog.startObservingHealth(observer4, Arrays.asList(APP_D), SHORT_DURATION);

        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B,
                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_C,
                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_D,
                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);

        assertThat(observer1.getLastFailureReason()).isEqualTo(
                PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
        assertThat(observer2.getLastFailureReason()).isEqualTo(
                PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
        assertThat(observer3.getLastFailureReason()).isEqualTo(
                PackageWatchdog.FAILURE_REASON_APP_CRASH);
        assertThat(observer4.getLastFailureReason()).isEqualTo(
                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
    }

    private void adoptShellPermissions(String... permissions) {
        InstrumentationRegistry
                .getInstrumentation()
@@ -900,9 +962,9 @@ public class PackageWatchdogTest {

    /** Trigger package failures above the threshold. */
    private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog,
            List<VersionedPackage> packages) {
            List<VersionedPackage> packages, int failureReason) {
        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
            watchdog.onPackageFailure(packages);
            watchdog.onPackageFailure(packages, failureReason);
        }
        mTestLooper.dispatchAll();
    }
@@ -936,6 +998,7 @@ public class PackageWatchdogTest {
    private static class TestObserver implements PackageHealthObserver {
        private final String mName;
        private int mImpact;
        private int mLastFailureReason;
        final List<String> mHealthCheckFailedPackages = new ArrayList<>();
        final List<String> mMitigatedPackages = new ArrayList<>();

@@ -954,14 +1017,19 @@ public class PackageWatchdogTest {
            return mImpact;
        }

        public boolean execute(VersionedPackage versionedPackage) {
        public boolean execute(VersionedPackage versionedPackage, int failureReason) {
            mMitigatedPackages.add(versionedPackage.getPackageName());
            mLastFailureReason = failureReason;
            return true;
        }

        public String getName() {
            return mName;
        }

        public int getLastFailureReason() {
            return mLastFailureReason;
        }
    }

    private static class TestController extends ExplicitHealthCheckController {