Merge "Integrate Rescue Party with Package Watchdog"
This commit is contained in:
committed by
Android (Google) Code Review
commit
3df3e3b0bf
@@ -199,13 +199,14 @@ public class PackageWatchdog {
|
||||
mSystemClock = clock;
|
||||
mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
|
||||
loadFromFile();
|
||||
sPackageWatchdog = this;
|
||||
}
|
||||
|
||||
/** Creates or gets singleton instance of PackageWatchdog. */
|
||||
public static PackageWatchdog getInstance(Context context) {
|
||||
synchronized (PackageWatchdog.class) {
|
||||
if (sPackageWatchdog == null) {
|
||||
sPackageWatchdog = new PackageWatchdog(context);
|
||||
new PackageWatchdog(context);
|
||||
}
|
||||
return sPackageWatchdog;
|
||||
}
|
||||
|
||||
@@ -18,8 +18,12 @@ package com.android.server;
|
||||
|
||||
import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.VersionedPackage;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.FileUtils;
|
||||
@@ -36,8 +40,12 @@ import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.StatsLog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.server.PackageWatchdog.FailureReasons;
|
||||
import com.android.server.PackageWatchdog.PackageHealthObserver;
|
||||
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
|
||||
import com.android.server.am.SettingsToPropertiesMapper;
|
||||
import com.android.server.utils.FlagNamespaceUtils;
|
||||
|
||||
@@ -79,19 +87,30 @@ public class RescueParty {
|
||||
@VisibleForTesting
|
||||
static final long BOOT_TRIGGER_WINDOW_MILLIS = 600 * DateUtils.SECOND_IN_MILLIS;
|
||||
@VisibleForTesting
|
||||
static final long PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS = 30 * DateUtils.SECOND_IN_MILLIS;
|
||||
@VisibleForTesting
|
||||
static final String TAG = "RescueParty";
|
||||
|
||||
private static final String NAME = "rescue-party-observer";
|
||||
|
||||
|
||||
private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
|
||||
private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start";
|
||||
private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
|
||||
|
||||
private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
|
||||
| ApplicationInfo.FLAG_SYSTEM;
|
||||
|
||||
|
||||
/** Threshold for boot loops */
|
||||
private static final Threshold sBoot = new BootThreshold();
|
||||
/** Threshold for app crash loops */
|
||||
private static SparseArray<Threshold> sApps = new SparseArray<>();
|
||||
|
||||
/** Register the Rescue Party observer as a Package Watchdog health observer */
|
||||
public static void registerHealthObserver(Context context) {
|
||||
PackageWatchdog.getInstance(context).registerHealthObserver(
|
||||
RescuePartyObserver.getInstance(context));
|
||||
}
|
||||
|
||||
private static boolean isDisabled() {
|
||||
// Check if we're explicitly enabled for testing
|
||||
if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) {
|
||||
@@ -134,24 +153,6 @@ public class RescueParty {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take note of a persistent app or apex module crash. If we notice too many of these
|
||||
* events happening in rapid succession, we'll send out a rescue party.
|
||||
*/
|
||||
public static void noteAppCrash(Context context, int uid) {
|
||||
if (isDisabled()) return;
|
||||
Threshold t = sApps.get(uid);
|
||||
if (t == null) {
|
||||
t = new AppThreshold(uid);
|
||||
sApps.put(uid, t);
|
||||
}
|
||||
if (t.incrementAndTest()) {
|
||||
t.reset();
|
||||
incrementRescueLevel(t.uid);
|
||||
executeRescueLevel(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we're currently attempting to reboot for a factory reset.
|
||||
*/
|
||||
@@ -171,11 +172,6 @@ public class RescueParty {
|
||||
@VisibleForTesting
|
||||
static void resetAllThresholds() {
|
||||
sBoot.reset();
|
||||
|
||||
for (int i = 0; i < sApps.size(); i++) {
|
||||
Threshold appThreshold = sApps.get(sApps.keyAt(i));
|
||||
appThreshold.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -243,6 +239,28 @@ public class RescueParty {
|
||||
FlagNamespaceUtils.NAMESPACE_NO_PACKAGE);
|
||||
}
|
||||
|
||||
private static int mapRescueLevelToUserImpact(int rescueLevel) {
|
||||
switch(rescueLevel) {
|
||||
case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
|
||||
case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
|
||||
return PackageHealthObserverImpact.USER_IMPACT_LOW;
|
||||
case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
|
||||
case LEVEL_FACTORY_RESET:
|
||||
return PackageHealthObserverImpact.USER_IMPACT_HIGH;
|
||||
default:
|
||||
return PackageHealthObserverImpact.USER_IMPACT_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getPackageUid(Context context, String packageName) {
|
||||
try {
|
||||
return context.getPackageManager().getPackageUid(packageName, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Since UIDs are always >= 0, this value means the UID could not be determined.
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void resetAllSettings(Context context, int mode) throws Exception {
|
||||
// Try our best to reset all settings possible, and once finished
|
||||
// rethrow any exception that we encountered
|
||||
@@ -270,6 +288,89 @@ public class RescueParty {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mitigation action for package failures. This observer will be register to Package
|
||||
* Watchdog and will receive calls about package failures. This observer is persistent so it
|
||||
* may choose to mitigate failures for packages it has not explicitly asked to observe.
|
||||
*/
|
||||
public static class RescuePartyObserver implements PackageHealthObserver {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
@GuardedBy("RescuePartyObserver.class")
|
||||
static RescuePartyObserver sRescuePartyObserver;
|
||||
|
||||
private RescuePartyObserver(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/** Creates or gets singleton instance of RescueParty. */
|
||||
public static RescuePartyObserver getInstance(Context context) {
|
||||
synchronized (RescuePartyObserver.class) {
|
||||
if (sRescuePartyObserver == null) {
|
||||
sRescuePartyObserver = new RescuePartyObserver(context);
|
||||
}
|
||||
return sRescuePartyObserver;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
|
||||
@FailureReasons int failureReason) {
|
||||
if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
|
||||
|| failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
|
||||
int rescueLevel = MathUtils.constrain(
|
||||
SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1,
|
||||
LEVEL_NONE, LEVEL_FACTORY_RESET);
|
||||
return mapRescueLevelToUserImpact(rescueLevel);
|
||||
} else {
|
||||
return PackageHealthObserverImpact.USER_IMPACT_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(@Nullable VersionedPackage failedPackage,
|
||||
@FailureReasons int failureReason) {
|
||||
if (isDisabled()) {
|
||||
return false;
|
||||
}
|
||||
if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
|
||||
|| failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
|
||||
int triggerUid = getPackageUid(mContext, failedPackage.getPackageName());
|
||||
incrementRescueLevel(triggerUid);
|
||||
executeRescueLevel(mContext);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPersistent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mayObservePackage(String packageName) {
|
||||
PackageManager pm = mContext.getPackageManager();
|
||||
try {
|
||||
// A package is a Mainline module if this is non-null
|
||||
if (pm.getModuleInfo(packageName, 0) != null) {
|
||||
return true;
|
||||
}
|
||||
ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
|
||||
return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Threshold that can be triggered if a number of events occur within a
|
||||
* window of time.
|
||||
@@ -349,27 +450,6 @@ public class RescueParty {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specialization of {@link Threshold} for monitoring app crashes. It stores
|
||||
* counters in memory.
|
||||
*/
|
||||
private static class AppThreshold extends Threshold {
|
||||
private int count;
|
||||
private long start;
|
||||
|
||||
public AppThreshold(int uid) {
|
||||
// We're interested in TRIGGER_COUNT events in any
|
||||
// PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS second period; apps crash pretty quickly
|
||||
// so we can keep a tight leash on them.
|
||||
super(uid, TRIGGER_COUNT, PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS);
|
||||
}
|
||||
|
||||
@Override public int getCount() { return count; }
|
||||
@Override public void setCount(int count) { this.count = count; }
|
||||
@Override public long getStart() { return start; }
|
||||
@Override public void setStart(long start) { this.start = start; }
|
||||
}
|
||||
|
||||
private static int[] getAllUserIds() {
|
||||
int[] userIds = { UserHandle.USER_SYSTEM };
|
||||
try {
|
||||
|
||||
@@ -33,8 +33,6 @@ import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.ModuleInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.VersionedPackage;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
@@ -56,7 +54,6 @@ import com.android.internal.app.ProcessMap;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.nano.MetricsProto;
|
||||
import com.android.server.PackageWatchdog;
|
||||
import com.android.server.RescueParty;
|
||||
import com.android.server.wm.WindowProcessController;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
@@ -423,28 +420,6 @@ class AppErrors {
|
||||
}
|
||||
|
||||
if (r != null) {
|
||||
boolean isApexModule = false;
|
||||
try {
|
||||
for (String androidPackage : r.getPackageList()) {
|
||||
ModuleInfo moduleInfo = mContext.getPackageManager().getModuleInfo(
|
||||
androidPackage, /*flags=*/ 0);
|
||||
if (moduleInfo != null) {
|
||||
isApexModule = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IllegalStateException | PackageManager.NameNotFoundException e) {
|
||||
// Call to PackageManager#getModuleInfo() can result in NameNotFoundException or
|
||||
// IllegalStateException. In case they are thrown, there isn't much we can do
|
||||
// other than proceed with app crash handling.
|
||||
}
|
||||
|
||||
if (r.isPersistent() || isApexModule) {
|
||||
// If a persistent app or apex module is stuck in a crash loop, the device isn't
|
||||
// very usable, so we want to consider sending out a rescue party.
|
||||
RescueParty.noteAppCrash(mContext, r.uid);
|
||||
}
|
||||
|
||||
mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode(),
|
||||
PackageWatchdog.FAILURE_REASON_APP_CRASH);
|
||||
}
|
||||
|
||||
@@ -751,6 +751,7 @@ public final class SystemServer {
|
||||
// Now that we have the bare essentials of the OS up and running, take
|
||||
// note that we just booted, which might send out a rescue party if
|
||||
// we're stuck in a runtime restart loop.
|
||||
RescueParty.registerHealthObserver(mSystemContext);
|
||||
RescueParty.noteBoot(mSystemContext);
|
||||
|
||||
// Manages LEDs and display backlight so we need it to bring up the display.
|
||||
|
||||
@@ -32,6 +32,7 @@ import static org.mockito.ArgumentMatchers.isNull;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.VersionedPackage;
|
||||
import android.os.RecoverySystem;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
@@ -39,6 +40,8 @@ import android.provider.DeviceConfig;
|
||||
import android.provider.Settings;
|
||||
|
||||
import com.android.dx.mockito.inline.extended.ExtendedMockito;
|
||||
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
|
||||
import com.android.server.RescueParty.RescuePartyObserver;
|
||||
import com.android.server.am.SettingsToPropertiesMapper;
|
||||
import com.android.server.utils.FlagNamespaceUtils;
|
||||
|
||||
@@ -57,13 +60,15 @@ import java.util.HashMap;
|
||||
* Test RescueParty.
|
||||
*/
|
||||
public class RescuePartyTest {
|
||||
private static final int PERSISTENT_APP_UID = 12;
|
||||
private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;
|
||||
private static final String FAKE_NATIVE_NAMESPACE1 = "native1";
|
||||
private static final String FAKE_NATIVE_NAMESPACE2 = "native2";
|
||||
private static final String[] FAKE_RESET_NATIVE_NAMESPACES =
|
||||
{FAKE_NATIVE_NAMESPACE1, FAKE_NATIVE_NAMESPACE2};
|
||||
|
||||
private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1);
|
||||
private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
|
||||
|
||||
private MockitoSession mSession;
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
@@ -182,25 +187,25 @@ public class RescuePartyTest {
|
||||
|
||||
@Test
|
||||
public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
|
||||
notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
|
||||
notePersistentAppCrash();
|
||||
|
||||
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
|
||||
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
|
||||
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
|
||||
|
||||
notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
|
||||
notePersistentAppCrash();
|
||||
|
||||
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES);
|
||||
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
|
||||
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
|
||||
|
||||
notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
|
||||
notePersistentAppCrash();
|
||||
|
||||
verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS);
|
||||
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
|
||||
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
|
||||
|
||||
notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
|
||||
notePersistentAppCrash();
|
||||
|
||||
verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
|
||||
assertEquals(RescueParty.LEVEL_FACTORY_RESET,
|
||||
@@ -220,20 +225,6 @@ public class RescuePartyTest {
|
||||
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPersistentAppCrashDetectionWithWrongInterval() {
|
||||
notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
|
||||
|
||||
// last persistent app crash is just outside of the boot loop detection window
|
||||
doReturn(CURRENT_NETWORK_TIME_MILLIS
|
||||
+ RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS + 1)
|
||||
.when(() -> RescueParty.getElapsedRealtime());
|
||||
notePersistentAppCrash(/*numTimes=*/1);
|
||||
|
||||
assertEquals(RescueParty.LEVEL_NONE,
|
||||
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBootLoopDetectionWithProperInterval() {
|
||||
noteBoot(RescueParty.TRIGGER_COUNT - 1);
|
||||
@@ -248,21 +239,6 @@ public class RescuePartyTest {
|
||||
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPersistentAppCrashDetectionWithProperInterval() {
|
||||
notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
|
||||
|
||||
// last persistent app crash is just inside of the boot loop detection window
|
||||
doReturn(CURRENT_NETWORK_TIME_MILLIS
|
||||
+ RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS)
|
||||
.when(() -> RescueParty.getElapsedRealtime());
|
||||
notePersistentAppCrash(/*numTimes=*/1);
|
||||
|
||||
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
|
||||
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
|
||||
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBootLoopDetectionWithWrongTriggerCount() {
|
||||
noteBoot(RescueParty.TRIGGER_COUNT - 1);
|
||||
@@ -270,13 +246,6 @@ public class RescuePartyTest {
|
||||
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPersistentAppCrashDetectionWithWrongTriggerCount() {
|
||||
notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
|
||||
assertEquals(RescueParty.LEVEL_NONE,
|
||||
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsAttemptingFactoryReset() {
|
||||
noteBoot(RescueParty.TRIGGER_COUNT * 4);
|
||||
@@ -319,6 +288,77 @@ public class RescuePartyTest {
|
||||
FAKE_NATIVE_NAMESPACE2, /*makeDefault=*/true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplicitlyEnablingAndDisablingRescue() {
|
||||
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
|
||||
SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
|
||||
assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
|
||||
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), false);
|
||||
|
||||
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
|
||||
assertTrue(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
|
||||
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHealthCheckLevels() {
|
||||
RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
|
||||
|
||||
// Ensure that no action is taken for cases where the failure reason is unknown
|
||||
SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
|
||||
RescueParty.LEVEL_FACTORY_RESET));
|
||||
assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN),
|
||||
PackageHealthObserverImpact.USER_IMPACT_NONE);
|
||||
|
||||
/*
|
||||
For the following cases, ensure that the returned user impact corresponds with the user
|
||||
impact of the next available rescue level, not the current one.
|
||||
*/
|
||||
SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
|
||||
RescueParty.LEVEL_NONE));
|
||||
assertEquals(observer.onHealthCheckFailed(null,
|
||||
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
|
||||
PackageHealthObserverImpact.USER_IMPACT_LOW);
|
||||
|
||||
SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
|
||||
RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS));
|
||||
assertEquals(observer.onHealthCheckFailed(null,
|
||||
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
|
||||
PackageHealthObserverImpact.USER_IMPACT_LOW);
|
||||
|
||||
|
||||
SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
|
||||
RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES));
|
||||
assertEquals(observer.onHealthCheckFailed(null,
|
||||
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
|
||||
PackageHealthObserverImpact.USER_IMPACT_HIGH);
|
||||
|
||||
|
||||
SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
|
||||
RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS));
|
||||
assertEquals(observer.onHealthCheckFailed(null,
|
||||
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
|
||||
PackageHealthObserverImpact.USER_IMPACT_HIGH);
|
||||
|
||||
|
||||
SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
|
||||
RescueParty.LEVEL_FACTORY_RESET));
|
||||
assertEquals(observer.onHealthCheckFailed(null,
|
||||
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
|
||||
PackageHealthObserverImpact.USER_IMPACT_HIGH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRescueLevelIncrementsWhenExecuted() {
|
||||
RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
|
||||
SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
|
||||
RescueParty.LEVEL_NONE));
|
||||
observer.execute(sFailingPackage,
|
||||
PackageWatchdog.FAILURE_REASON_APP_CRASH);
|
||||
assertEquals(SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, -1),
|
||||
RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS);
|
||||
}
|
||||
|
||||
private void verifySettingsResets(int resetMode) {
|
||||
verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,
|
||||
resetMode, UserHandle.USER_SYSTEM));
|
||||
@@ -332,9 +372,8 @@ public class RescuePartyTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void notePersistentAppCrash(int numTimes) {
|
||||
for (int i = 0; i < numTimes; i++) {
|
||||
RescueParty.noteAppCrash(mMockContext, PERSISTENT_APP_UID);
|
||||
}
|
||||
private void notePersistentAppCrash() {
|
||||
RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage(
|
||||
"com.package.name", 1), PackageWatchdog.FAILURE_REASON_UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -896,37 +896,76 @@ public class PackageWatchdogTest {
|
||||
assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
|
||||
}
|
||||
|
||||
/** Test that observers execute correctly for different failure reasons */
|
||||
/** Test that observers execute correctly for failures reasons that go through thresholding. */
|
||||
@Test
|
||||
public void testFailureReasons() {
|
||||
public void testNonImmediateFailureReasons() {
|
||||
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_APP_CRASH);
|
||||
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B,
|
||||
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
|
||||
|
||||
assertThat(observer1.getLastFailureReason()).isEqualTo(
|
||||
PackageWatchdog.FAILURE_REASON_APP_CRASH);
|
||||
assertThat(observer2.getLastFailureReason()).isEqualTo(
|
||||
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
|
||||
}
|
||||
|
||||
/** Test that observers execute correctly for failures reasons that skip thresholding. */
|
||||
@Test
|
||||
public void testImmediateFailures() {
|
||||
PackageWatchdog watchdog = createWatchdog();
|
||||
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
|
||||
|
||||
watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), 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);
|
||||
assertThat(observer1.mMitigatedPackages).containsExactly(APP_A, APP_B);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a persistent observer will mitigate failures if it wishes to observe a package.
|
||||
*/
|
||||
@Test
|
||||
public void testPersistentObserverWatchesPackage() {
|
||||
PackageWatchdog watchdog = createWatchdog();
|
||||
TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1);
|
||||
persistentObserver.setPersistent(true);
|
||||
persistentObserver.setMayObservePackages(true);
|
||||
|
||||
watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
|
||||
|
||||
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
|
||||
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
|
||||
assertThat(persistentObserver.mHealthCheckFailedPackages).containsExactly(APP_A);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a persistent observer will not mitigate failures if it does not wish to observe
|
||||
* a given package.
|
||||
*/
|
||||
@Test
|
||||
public void testPersistentObserverDoesNotWatchPackage() {
|
||||
PackageWatchdog watchdog = createWatchdog();
|
||||
TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1);
|
||||
persistentObserver.setPersistent(true);
|
||||
persistentObserver.setMayObservePackages(false);
|
||||
|
||||
watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
|
||||
|
||||
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
|
||||
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
|
||||
assertThat(persistentObserver.mHealthCheckFailedPackages).isEmpty();
|
||||
}
|
||||
|
||||
private void adoptShellPermissions(String... permissions) {
|
||||
@@ -964,7 +1003,12 @@ public class PackageWatchdogTest {
|
||||
/** Trigger package failures above the threshold. */
|
||||
private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog,
|
||||
List<VersionedPackage> packages, int failureReason) {
|
||||
for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
|
||||
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();
|
||||
@@ -1000,6 +1044,8 @@ public class PackageWatchdogTest {
|
||||
private final String mName;
|
||||
private int mImpact;
|
||||
private int mLastFailureReason;
|
||||
private boolean mIsPersistent = false;
|
||||
private boolean mMayObservePackages = false;
|
||||
final List<String> mHealthCheckFailedPackages = new ArrayList<>();
|
||||
final List<String> mMitigatedPackages = new ArrayList<>();
|
||||
|
||||
@@ -1028,9 +1074,25 @@ public class PackageWatchdogTest {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public boolean isPersistent() {
|
||||
return mIsPersistent;
|
||||
}
|
||||
|
||||
public boolean mayObservePackage(String packageName) {
|
||||
return mMayObservePackages;
|
||||
}
|
||||
|
||||
public int getLastFailureReason() {
|
||||
return mLastFailureReason;
|
||||
}
|
||||
|
||||
public void setPersistent(boolean persistent) {
|
||||
mIsPersistent = persistent;
|
||||
}
|
||||
|
||||
public void setMayObservePackages(boolean mayObservePackages) {
|
||||
mMayObservePackages = mayObservePackages;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestController extends ExplicitHealthCheckController {
|
||||
|
||||
Reference in New Issue
Block a user