Loading services/core/java/com/android/server/RescueParty.java +30 −12 Original line number Diff line number Diff line Loading @@ -79,6 +79,7 @@ public class RescueParty { static final String PROP_ATTEMPTING_FACTORY_RESET = "sys.attempting_factory_reset"; static final String PROP_ATTEMPTING_REBOOT = "sys.attempting_reboot"; static final String PROP_MAX_RESCUE_LEVEL_ATTEMPTED = "sys.max_rescue_level_attempted"; static final String PROP_LAST_FACTORY_RESET_TIME_MS = "persist.sys.last_factory_reset"; @VisibleForTesting static final int LEVEL_NONE = 0; @VisibleForTesting Loading @@ -105,10 +106,11 @@ public class RescueParty { @VisibleForTesting static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG = "namespace_to_package_mapping"; @VisibleForTesting static final long FACTORY_RESET_THROTTLE_DURATION_MS = TimeUnit.MINUTES.toMillis(10); private static final String NAME = "rescue-party-observer"; private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device"; private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG = Loading Loading @@ -327,8 +329,8 @@ public class RescueParty { } } private static int getMaxRescueLevel(boolean mayPerformFactoryReset) { if (!mayPerformFactoryReset private static int getMaxRescueLevel(boolean mayPerformReboot) { if (!mayPerformReboot || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) { return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS; } Loading @@ -339,11 +341,11 @@ public class RescueParty { * Get the rescue level to perform if this is the n-th attempt at mitigating failure. * * @param mitigationCount: the mitigation attempt number (1 = first attempt etc.) * @param mayPerformFactoryReset: whether or not a factory reset may be performed for the given * failure. * @param mayPerformReboot: whether or not a reboot and factory reset may be performed * for the given failure. * @return the rescue level for the n-th mitigation attempt. */ private static int getRescueLevel(int mitigationCount, boolean mayPerformFactoryReset) { private static int getRescueLevel(int mitigationCount, boolean mayPerformReboot) { if (mitigationCount == 1) { return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS; } else if (mitigationCount == 2) { Loading @@ -351,9 +353,9 @@ public class RescueParty { } else if (mitigationCount == 3) { return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS; } else if (mitigationCount == 4) { return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_WARM_REBOOT); return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_WARM_REBOOT); } else if (mitigationCount >= 5) { return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_FACTORY_RESET); return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_FACTORY_RESET); } else { Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount); return LEVEL_NONE; Loading Loading @@ -450,6 +452,8 @@ public class RescueParty { break; } SystemProperties.set(PROP_ATTEMPTING_FACTORY_RESET, "true"); long now = System.currentTimeMillis(); SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(now)); runnable = new Runnable() { @Override public void run() { Loading Loading @@ -627,7 +631,7 @@ public class RescueParty { if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) { return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, mayPerformFactoryReset(failedPackage))); mayPerformReboot(failedPackage))); } else { return PackageHealthObserverImpact.USER_IMPACT_NONE; } Loading @@ -642,7 +646,7 @@ public class RescueParty { if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) { final int level = getRescueLevel(mitigationCount, mayPerformFactoryReset(failedPackage)); mayPerformReboot(failedPackage)); executeRescueLevel(mContext, failedPackage == null ? null : failedPackage.getPackageName(), level); return true; Loading Loading @@ -683,8 +687,9 @@ public class RescueParty { if (isDisabled()) { return false; } boolean mayPerformReboot = !shouldThrottleReboot(); executeRescueLevel(mContext, /*failedPackage=*/ null, getRescueLevel(mitigationCount, true)); getRescueLevel(mitigationCount, mayPerformReboot)); return true; } Loading @@ -698,14 +703,27 @@ public class RescueParty { * prompting a factory reset is an acceptable mitigation strategy for the package's * failure, {@code false} otherwise. */ private boolean mayPerformFactoryReset(@Nullable VersionedPackage failingPackage) { private boolean mayPerformReboot(@Nullable VersionedPackage failingPackage) { if (failingPackage == null) { return false; } if (shouldThrottleReboot()) { return false; } return isPersistentSystemApp(failingPackage.getPackageName()); } /** * Returns {@code true} if Rescue Party is allowed to attempt a reboot or factory reset. * Will return {@code false} if a factory reset was already offered recently. */ private boolean shouldThrottleReboot() { Long lastResetTime = SystemProperties.getLong(PROP_LAST_FACTORY_RESET_TIME_MS, 0); long now = System.currentTimeMillis(); return now < lastResetTime + FACTORY_RESET_THROTTLE_DURATION_MS; } private boolean isPersistentSystemApp(@NonNull String packageName) { PackageManager pm = mContext.getPackageManager(); try { Loading services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +51 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; /** * Test RescueParty. Loading @@ -94,6 +95,9 @@ public class RescuePartyTest { "persist.device_config.configuration.disable_rescue_party"; private static final String PROP_DISABLE_FACTORY_RESET_FLAG = "persist.device_config.configuration.disable_rescue_party_factory_reset"; private static final String PROP_LAST_FACTORY_RESET_TIME_MS = "persist.sys.last_factory_reset"; private static final int THROTTLING_DURATION_MIN = 10; private MockitoSession mSession; private HashMap<String, String> mSystemSettingsMap; Loading Loading @@ -458,6 +462,53 @@ public class RescuePartyTest { assertTrue(RescueParty.isFactoryResetPropertySet()); } @Test public void testThrottlingOnBootFailures() { SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false)); long now = System.currentTimeMillis(); long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1); SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(beforeTimeout)); for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) { noteBoot(i); } assertFalse(RescueParty.isAttemptingFactoryReset()); } @Test public void testThrottlingOnAppCrash() { SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false)); long now = System.currentTimeMillis(); long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1); SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(beforeTimeout)); for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) { noteAppCrash(i + 1, true); } assertFalse(RescueParty.isAttemptingFactoryReset()); } @Test public void testNotThrottlingAfterTimeoutOnBootFailures() { SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false)); long now = System.currentTimeMillis(); long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1); SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(afterTimeout)); for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) { noteBoot(i); } assertTrue(RescueParty.isAttemptingFactoryReset()); } @Test public void testNotThrottlingAfterTimeoutOnAppCrash() { SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false)); long now = System.currentTimeMillis(); long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1); SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(afterTimeout)); for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) { noteAppCrash(i + 1, true); } assertTrue(RescueParty.isAttemptingFactoryReset()); } @Test public void testNativeRescuePartyResets() { doReturn(true).when(() -> SettingsToPropertiesMapper.isNativeFlagsResetPerformed()); Loading Loading
services/core/java/com/android/server/RescueParty.java +30 −12 Original line number Diff line number Diff line Loading @@ -79,6 +79,7 @@ public class RescueParty { static final String PROP_ATTEMPTING_FACTORY_RESET = "sys.attempting_factory_reset"; static final String PROP_ATTEMPTING_REBOOT = "sys.attempting_reboot"; static final String PROP_MAX_RESCUE_LEVEL_ATTEMPTED = "sys.max_rescue_level_attempted"; static final String PROP_LAST_FACTORY_RESET_TIME_MS = "persist.sys.last_factory_reset"; @VisibleForTesting static final int LEVEL_NONE = 0; @VisibleForTesting Loading @@ -105,10 +106,11 @@ public class RescueParty { @VisibleForTesting static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG = "namespace_to_package_mapping"; @VisibleForTesting static final long FACTORY_RESET_THROTTLE_DURATION_MS = TimeUnit.MINUTES.toMillis(10); private static final String NAME = "rescue-party-observer"; private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device"; private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG = Loading Loading @@ -327,8 +329,8 @@ public class RescueParty { } } private static int getMaxRescueLevel(boolean mayPerformFactoryReset) { if (!mayPerformFactoryReset private static int getMaxRescueLevel(boolean mayPerformReboot) { if (!mayPerformReboot || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) { return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS; } Loading @@ -339,11 +341,11 @@ public class RescueParty { * Get the rescue level to perform if this is the n-th attempt at mitigating failure. * * @param mitigationCount: the mitigation attempt number (1 = first attempt etc.) * @param mayPerformFactoryReset: whether or not a factory reset may be performed for the given * failure. * @param mayPerformReboot: whether or not a reboot and factory reset may be performed * for the given failure. * @return the rescue level for the n-th mitigation attempt. */ private static int getRescueLevel(int mitigationCount, boolean mayPerformFactoryReset) { private static int getRescueLevel(int mitigationCount, boolean mayPerformReboot) { if (mitigationCount == 1) { return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS; } else if (mitigationCount == 2) { Loading @@ -351,9 +353,9 @@ public class RescueParty { } else if (mitigationCount == 3) { return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS; } else if (mitigationCount == 4) { return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_WARM_REBOOT); return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_WARM_REBOOT); } else if (mitigationCount >= 5) { return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_FACTORY_RESET); return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_FACTORY_RESET); } else { Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount); return LEVEL_NONE; Loading Loading @@ -450,6 +452,8 @@ public class RescueParty { break; } SystemProperties.set(PROP_ATTEMPTING_FACTORY_RESET, "true"); long now = System.currentTimeMillis(); SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(now)); runnable = new Runnable() { @Override public void run() { Loading Loading @@ -627,7 +631,7 @@ public class RescueParty { if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) { return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, mayPerformFactoryReset(failedPackage))); mayPerformReboot(failedPackage))); } else { return PackageHealthObserverImpact.USER_IMPACT_NONE; } Loading @@ -642,7 +646,7 @@ public class RescueParty { if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) { final int level = getRescueLevel(mitigationCount, mayPerformFactoryReset(failedPackage)); mayPerformReboot(failedPackage)); executeRescueLevel(mContext, failedPackage == null ? null : failedPackage.getPackageName(), level); return true; Loading Loading @@ -683,8 +687,9 @@ public class RescueParty { if (isDisabled()) { return false; } boolean mayPerformReboot = !shouldThrottleReboot(); executeRescueLevel(mContext, /*failedPackage=*/ null, getRescueLevel(mitigationCount, true)); getRescueLevel(mitigationCount, mayPerformReboot)); return true; } Loading @@ -698,14 +703,27 @@ public class RescueParty { * prompting a factory reset is an acceptable mitigation strategy for the package's * failure, {@code false} otherwise. */ private boolean mayPerformFactoryReset(@Nullable VersionedPackage failingPackage) { private boolean mayPerformReboot(@Nullable VersionedPackage failingPackage) { if (failingPackage == null) { return false; } if (shouldThrottleReboot()) { return false; } return isPersistentSystemApp(failingPackage.getPackageName()); } /** * Returns {@code true} if Rescue Party is allowed to attempt a reboot or factory reset. * Will return {@code false} if a factory reset was already offered recently. */ private boolean shouldThrottleReboot() { Long lastResetTime = SystemProperties.getLong(PROP_LAST_FACTORY_RESET_TIME_MS, 0); long now = System.currentTimeMillis(); return now < lastResetTime + FACTORY_RESET_THROTTLE_DURATION_MS; } private boolean isPersistentSystemApp(@NonNull String packageName) { PackageManager pm = mContext.getPackageManager(); try { Loading
services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +51 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; /** * Test RescueParty. Loading @@ -94,6 +95,9 @@ public class RescuePartyTest { "persist.device_config.configuration.disable_rescue_party"; private static final String PROP_DISABLE_FACTORY_RESET_FLAG = "persist.device_config.configuration.disable_rescue_party_factory_reset"; private static final String PROP_LAST_FACTORY_RESET_TIME_MS = "persist.sys.last_factory_reset"; private static final int THROTTLING_DURATION_MIN = 10; private MockitoSession mSession; private HashMap<String, String> mSystemSettingsMap; Loading Loading @@ -458,6 +462,53 @@ public class RescuePartyTest { assertTrue(RescueParty.isFactoryResetPropertySet()); } @Test public void testThrottlingOnBootFailures() { SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false)); long now = System.currentTimeMillis(); long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1); SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(beforeTimeout)); for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) { noteBoot(i); } assertFalse(RescueParty.isAttemptingFactoryReset()); } @Test public void testThrottlingOnAppCrash() { SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false)); long now = System.currentTimeMillis(); long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1); SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(beforeTimeout)); for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) { noteAppCrash(i + 1, true); } assertFalse(RescueParty.isAttemptingFactoryReset()); } @Test public void testNotThrottlingAfterTimeoutOnBootFailures() { SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false)); long now = System.currentTimeMillis(); long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1); SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(afterTimeout)); for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) { noteBoot(i); } assertTrue(RescueParty.isAttemptingFactoryReset()); } @Test public void testNotThrottlingAfterTimeoutOnAppCrash() { SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false)); long now = System.currentTimeMillis(); long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1); SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(afterTimeout)); for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) { noteAppCrash(i + 1, true); } assertTrue(RescueParty.isAttemptingFactoryReset()); } @Test public void testNativeRescuePartyResets() { doReturn(true).when(() -> SettingsToPropertiesMapper.isNativeFlagsResetPerformed()); Loading