Merge "Constrain Non-DeviceConfig settings reset times & unify DeviceConfig reset mode"

This commit is contained in:
Hongyi Zhang
2020-11-11 06:04:40 +00:00
committed by Android (Google) Code Review
2 changed files with 161 additions and 38 deletions

View File

@@ -78,6 +78,7 @@ public class RescueParty {
@VisibleForTesting
static final String PROP_RESCUE_LEVEL = "sys.rescue_level";
static final String PROP_ATTEMPTING_FACTORY_RESET = "sys.attempting_factory_reset";
static final String PROP_MAX_RESCUE_LEVEL_ATTEMPTED = "sys.max_rescue_level_attempted";
@VisibleForTesting
static final int LEVEL_NONE = 0;
@VisibleForTesting
@@ -94,6 +95,8 @@ public class RescueParty {
static final String TAG = "RescueParty";
@VisibleForTesting
static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);
@VisibleForTesting
static final int DEVICE_CONFIG_RESET_MODE = Settings.RESET_MODE_TRUSTED_DEFAULTS;
private static final String NAME = "rescue-party-observer";
@@ -225,7 +228,7 @@ public class RescueParty {
if (NAMESPACE_CONFIGURATION.equals(resetNativeCategories[i])) {
continue;
}
DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
DeviceConfig.resetToDefaults(DEVICE_CONFIG_RESET_MODE,
resetNativeCategories[i]);
}
}
@@ -300,15 +303,48 @@ public class RescueParty {
private static void executeRescueLevelInternal(Context context, int level, @Nullable
String failedPackage) throws Exception {
FrameworkStatsLog.write(FrameworkStatsLog.RESCUE_PARTY_RESET_REPORTED, level);
// Try our best to reset all settings possible, and once finished
// rethrow any exception that we encountered
Exception res = null;
switch (level) {
case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS, failedPackage);
try {
resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS,
level);
} catch (Exception e) {
res = e;
}
try {
resetDeviceConfig(context, /*isScoped=*/true, failedPackage);
} catch (Exception e) {
res = e;
}
break;
case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_CHANGES, failedPackage);
try {
resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_CHANGES,
level);
} catch (Exception e) {
res = e;
}
try {
resetDeviceConfig(context, /*isScoped=*/true, failedPackage);
} catch (Exception e) {
res = e;
}
break;
case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
resetAllSettings(context, Settings.RESET_MODE_TRUSTED_DEFAULTS, failedPackage);
try {
resetAllSettingsIfNecessary(context, Settings.RESET_MODE_TRUSTED_DEFAULTS,
level);
} catch (Exception e) {
res = e;
}
try {
resetDeviceConfig(context, /*isScoped=*/false, failedPackage);
} catch (Exception e) {
res = e;
}
break;
case LEVEL_FACTORY_RESET:
// Request the reboot from a separate thread to avoid deadlock on PackageWatchdog
@@ -328,6 +364,10 @@ public class RescueParty {
thread.start();
break;
}
if (res != null) {
throw res;
}
}
private static void logRescueException(int level, Throwable t) {
@@ -350,17 +390,17 @@ public class RescueParty {
}
}
private static void resetAllSettings(Context context, int mode, @Nullable String failedPackage)
throws Exception {
private static void resetAllSettingsIfNecessary(Context context, int mode,
int level) throws Exception {
// No need to reset Settings again if they are already reset in the current level once.
if (SystemProperties.getInt(PROP_MAX_RESCUE_LEVEL_ATTEMPTED, LEVEL_NONE) >= level) {
return;
}
SystemProperties.set(PROP_MAX_RESCUE_LEVEL_ATTEMPTED, Integer.toString(level));
// Try our best to reset all settings possible, and once finished
// rethrow any exception that we encountered
Exception res = null;
final ContentResolver resolver = context.getContentResolver();
try {
resetDeviceConfig(context, mode, failedPackage);
} catch (Exception e) {
res = new RuntimeException("Failed to reset config settings", e);
}
try {
Settings.Global.resetToDefaultsAsUser(resolver, null, mode, UserHandle.USER_SYSTEM);
} catch (Exception e) {
@@ -378,16 +418,21 @@ public class RescueParty {
}
}
private static void resetDeviceConfig(Context context, int resetMode,
@Nullable String failedPackage) {
if (!shouldPerformScopedResets(resetMode) || failedPackage == null) {
resetAllAffectedNamespaces(context, resetMode);
} else {
performScopedReset(context, resetMode, failedPackage);
private static void resetDeviceConfig(Context context, boolean isScoped,
@Nullable String failedPackage) throws Exception {
final ContentResolver resolver = context.getContentResolver();
try {
if (!isScoped || failedPackage == null) {
resetAllAffectedNamespaces(context);
} else {
performScopedReset(context, failedPackage);
}
} catch (Exception e) {
throw new RuntimeException("Failed to reset config settings", e);
}
}
private static void resetAllAffectedNamespaces(Context context, int resetMode) {
private static void resetAllAffectedNamespaces(Context context) {
RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstance(context);
Set<String> allAffectedNamespaces = rescuePartyObserver.getAllAffectedNamespaceSet();
@@ -401,16 +446,11 @@ public class RescueParty {
if (NAMESPACE_CONFIGURATION.equals(namespace)) {
continue;
}
DeviceConfig.resetToDefaults(resetMode, namespace);
DeviceConfig.resetToDefaults(DEVICE_CONFIG_RESET_MODE, namespace);
}
}
private static boolean shouldPerformScopedResets(int resetMode) {
return resetMode <= Settings.RESET_MODE_UNTRUSTED_CHANGES;
}
private static void performScopedReset(Context context, int resetMode,
@NonNull String failedPackage) {
private static void performScopedReset(Context context, @NonNull String failedPackage) {
RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstance(context);
Set<String> affectedNamespaces = rescuePartyObserver.getAffectedNamespaceSet(
failedPackage);
@@ -428,7 +468,7 @@ public class RescueParty {
if (NAMESPACE_CONFIGURATION.equals(namespace)) {
continue;
}
DeviceConfig.resetToDefaults(resetMode, namespace);
DeviceConfig.resetToDefaults(DEVICE_CONFIG_RESET_MODE, namespace);
}
}
}

View File

@@ -191,10 +191,12 @@ public class RescuePartyTest {
RescueParty.onSettingsProviderPublished(mMockContext);
verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
mMonitorCallbackCaptor.capture()));
HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
noteBoot();
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
verifiedTimesMap);
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
@@ -208,13 +210,15 @@ public class RescuePartyTest {
noteBoot();
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, expectedAllResetNamespaces);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, expectedAllResetNamespaces,
verifiedTimesMap);
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
noteBoot();
verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces);
verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces,
verifiedTimesMap);
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
@@ -228,15 +232,18 @@ public class RescuePartyTest {
public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
notePersistentAppCrash(1);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
/*configResetVerifiedTimesMap=*/ null);
notePersistentAppCrash(2);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null,
/*configResetVerifiedTimesMap=*/ null);
notePersistentAppCrash(3);
verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null);
verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
/*configResetVerifiedTimesMap=*/ null);
notePersistentAppCrash(4);
assertTrue(RescueParty.isAttemptingFactoryReset());
@@ -272,17 +279,82 @@ public class RescuePartyTest {
final String[] expectedResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
final String[] expectedAllResetNamespaces =
new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3};
HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, expectedResetNamespaces);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, expectedResetNamespaces,
verifiedTimesMap);
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, expectedResetNamespaces);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, expectedResetNamespaces,
verifiedTimesMap);
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3);
verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces);
verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces,
verifiedTimesMap);
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
assertTrue(RescueParty.isAttemptingFactoryReset());
}
@Test
public void testNonDeviceConfigSettingsOnlyResetOncePerLevel() {
RescueParty.onSettingsProviderPublished(mMockContext);
verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
mMonitorCallbackCaptor.capture()));
// Record DeviceConfig accesses
RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2));
monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2));
monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3));
// Fake DeviceConfig value changes
monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1));
monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2));
monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3));
// Perform and verify scoped resets
final String[] expectedPackage1ResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
final String[] expectedPackage2ResetNamespaces = new String[]{NAMESPACE2, NAMESPACE3};
final String[] expectedAllResetNamespaces =
new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3};
HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS,
expectedPackage1ResetNamespaces, verifiedTimesMap);
// Settings.Global & Settings.Secure should still remain the same execution times.
observer.execute(new VersionedPackage(
CALLING_PACKAGE2, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS,
expectedPackage2ResetNamespaces, verifiedTimesMap);
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES,
expectedPackage1ResetNamespaces, verifiedTimesMap);
// Settings.Global & Settings.Secure should still remain the same execution times.
observer.execute(new VersionedPackage(
CALLING_PACKAGE2, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES,
expectedPackage2ResetNamespaces, verifiedTimesMap);
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3);
verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces,
verifiedTimesMap);
// Settings.Global & Settings.Secure should still remain the same execution times.
observer.execute(new VersionedPackage(
CALLING_PACKAGE2, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3);
verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces,
verifiedTimesMap);
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
@@ -303,7 +375,8 @@ public class RescuePartyTest {
RescueParty.onSettingsProviderPublished(mMockContext);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
/*configResetVerifiedTimesMap=*/ null);
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
}
@@ -415,7 +488,8 @@ public class RescuePartyTest {
assertEquals(observer.onBootLoop(), PackageHealthObserverImpact.USER_IMPACT_HIGH);
}
private void verifySettingsResets(int resetMode, String[] resetNamespaces) {
private void verifySettingsResets(int resetMode, String[] resetNamespaces,
HashMap<String, Integer> configResetVerifiedTimesMap) {
verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,
resetMode, UserHandle.USER_SYSTEM));
verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(),
@@ -425,7 +499,16 @@ public class RescuePartyTest {
verify(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()), never());
} else {
for (String namespace : resetNamespaces) {
verify(() -> DeviceConfig.resetToDefaults(resetMode, namespace));
int verifiedTimes = 0;
if (configResetVerifiedTimesMap != null
&& configResetVerifiedTimesMap.get(namespace) != null) {
verifiedTimes = configResetVerifiedTimesMap.get(namespace);
}
verify(() -> DeviceConfig.resetToDefaults(RescueParty.DEVICE_CONFIG_RESET_MODE,
namespace), times(verifiedTimes + 1));
if (configResetVerifiedTimesMap != null) {
configResetVerifiedTimesMap.put(namespace, verifiedTimes + 1);
}
}
}
}