1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server; 18 19 import static android.provider.DeviceConfig.Properties; 20 21 import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED; 22 import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS; 23 import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent; 24 25 import android.annotation.IntDef; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.content.ContentResolver; 29 import android.content.Context; 30 import android.content.pm.ApplicationInfo; 31 import android.content.pm.PackageManager; 32 import android.content.pm.VersionedPackage; 33 import android.crashrecovery.flags.Flags; 34 import android.os.Build; 35 import android.os.Environment; 36 import android.os.FileUtils; 37 import android.os.PowerManager; 38 import android.os.RecoverySystem; 39 import android.os.SystemClock; 40 import android.os.SystemProperties; 41 import android.os.UserHandle; 42 import android.provider.DeviceConfig; 43 import android.provider.Settings; 44 import android.sysprop.CrashRecoveryProperties; 45 import android.text.TextUtils; 46 import android.util.ArraySet; 47 import android.util.EventLog; 48 import android.util.Log; 49 import android.util.Slog; 50 51 import com.android.internal.annotations.GuardedBy; 52 import com.android.internal.annotations.VisibleForTesting; 53 import com.android.internal.util.ArrayUtils; 54 import com.android.server.PackageWatchdog.FailureReasons; 55 import com.android.server.PackageWatchdog.PackageHealthObserver; 56 import com.android.server.PackageWatchdog.PackageHealthObserverImpact; 57 import com.android.server.am.SettingsToPropertiesMapper; 58 import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog; 59 60 import java.io.File; 61 import java.lang.annotation.Retention; 62 import java.lang.annotation.RetentionPolicy; 63 import java.util.ArrayList; 64 import java.util.HashMap; 65 import java.util.HashSet; 66 import java.util.Iterator; 67 import java.util.List; 68 import java.util.Map; 69 import java.util.Set; 70 import java.util.concurrent.Executors; 71 import java.util.concurrent.TimeUnit; 72 73 /** 74 * Utilities to help rescue the system from crash loops. Callers are expected to 75 * report boot events and persistent app crashes, and if they happen frequently 76 * enough this class will slowly escalate through several rescue operations 77 * before finally rebooting and prompting the user if they want to wipe data as 78 * a last resort. 79 * 80 * @hide 81 */ 82 public class RescueParty { 83 @VisibleForTesting 84 static final String PROP_ENABLE_RESCUE = "persist.sys.enable_rescue"; 85 @VisibleForTesting 86 static final int LEVEL_NONE = 0; 87 @VisibleForTesting 88 static final int LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 1; 89 @VisibleForTesting 90 static final int LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 2; 91 @VisibleForTesting 92 static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3; 93 @VisibleForTesting 94 static final int LEVEL_WARM_REBOOT = 4; 95 @VisibleForTesting 96 static final int LEVEL_FACTORY_RESET = 5; 97 @VisibleForTesting 98 static final int RESCUE_LEVEL_NONE = 0; 99 @VisibleForTesting 100 static final int RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET = 1; 101 @VisibleForTesting 102 static final int RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET = 2; 103 @VisibleForTesting 104 static final int RESCUE_LEVEL_WARM_REBOOT = 3; 105 @VisibleForTesting 106 static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 4; 107 @VisibleForTesting 108 static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 5; 109 @VisibleForTesting 110 static final int RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 6; 111 @VisibleForTesting 112 static final int RESCUE_LEVEL_FACTORY_RESET = 7; 113 114 @IntDef(prefix = { "RESCUE_LEVEL_" }, value = { 115 RESCUE_LEVEL_NONE, 116 RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET, 117 RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET, 118 RESCUE_LEVEL_WARM_REBOOT, 119 RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS, 120 RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES, 121 RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS, 122 RESCUE_LEVEL_FACTORY_RESET 123 }) 124 @Retention(RetentionPolicy.SOURCE) 125 @interface RescueLevels {} 126 127 @VisibleForTesting 128 static final String RESCUE_NON_REBOOT_LEVEL_LIMIT = "persist.sys.rescue_non_reboot_level_limit"; 129 @VisibleForTesting 130 static final int DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT = RESCUE_LEVEL_WARM_REBOOT - 1; 131 @VisibleForTesting 132 static final String TAG = "RescueParty"; 133 @VisibleForTesting 134 static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2); 135 @VisibleForTesting 136 static final int DEVICE_CONFIG_RESET_MODE = Settings.RESET_MODE_TRUSTED_DEFAULTS; 137 // The DeviceConfig namespace containing all RescueParty switches. 138 @VisibleForTesting 139 static final String NAMESPACE_CONFIGURATION = "configuration"; 140 @VisibleForTesting 141 static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG = 142 "namespace_to_package_mapping"; 143 @VisibleForTesting 144 static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 1440; 145 146 private static final String NAME = "rescue-party-observer"; 147 148 private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; 149 private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device"; 150 private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG = 151 "persist.device_config.configuration.disable_rescue_party"; 152 private static final String PROP_DISABLE_FACTORY_RESET_FLAG = 153 "persist.device_config.configuration.disable_rescue_party_factory_reset"; 154 private static final String PROP_THROTTLE_DURATION_MIN_FLAG = 155 "persist.device_config.configuration.rescue_party_throttle_duration_min"; 156 157 private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT 158 | ApplicationInfo.FLAG_SYSTEM; 159 160 /** 161 * EventLog tags used when logging into the event log. Note the values must be sync with 162 * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct 163 * name translation. 164 */ 165 private static final int LOG_TAG_RESCUE_SUCCESS = 2902; 166 private static final int LOG_TAG_RESCUE_FAILURE = 2903; 167 168 /** Register the Rescue Party observer as a Package Watchdog health observer */ registerHealthObserver(Context context)169 public static void registerHealthObserver(Context context) { 170 PackageWatchdog.getInstance(context).registerHealthObserver( 171 null, RescuePartyObserver.getInstance(context)); 172 } 173 isDisabled()174 private static boolean isDisabled() { 175 // Check if we're explicitly enabled for testing 176 if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) { 177 return false; 178 } 179 180 // We're disabled if the DeviceConfig disable flag is set to true. 181 // This is in case that an emergency rollback of the feature is needed. 182 if (SystemProperties.getBoolean(PROP_DEVICE_CONFIG_DISABLE_FLAG, false)) { 183 Slog.v(TAG, "Disabled because of DeviceConfig flag"); 184 return true; 185 } 186 187 // We're disabled on all engineering devices 188 if (Build.TYPE.equals("eng")) { 189 Slog.v(TAG, "Disabled because of eng build"); 190 return true; 191 } 192 193 // We're disabled on userdebug devices connected over USB, since that's 194 // a decent signal that someone is actively trying to debug the device, 195 // or that it's in a lab environment. 196 if (Build.TYPE.equals("userdebug") && isUsbActive()) { 197 Slog.v(TAG, "Disabled because of active USB connection"); 198 return true; 199 } 200 201 // One last-ditch check 202 if (SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false)) { 203 Slog.v(TAG, "Disabled because of manual property"); 204 return true; 205 } 206 207 return false; 208 } 209 210 /** 211 * Check if we're currently attempting to reboot for a factory reset. This method must 212 * return true if RescueParty tries to reboot early during a boot loop, since the device 213 * will not be fully booted at this time. 214 */ isRecoveryTriggeredReboot()215 public static boolean isRecoveryTriggeredReboot() { 216 return isFactoryResetPropertySet() || isRebootPropertySet(); 217 } 218 isFactoryResetPropertySet()219 static boolean isFactoryResetPropertySet() { 220 return CrashRecoveryProperties.attemptingFactoryReset().orElse(false); 221 } 222 isRebootPropertySet()223 static boolean isRebootPropertySet() { 224 return CrashRecoveryProperties.attemptingReboot().orElse(false); 225 } 226 getLastFactoryResetTimeMs()227 protected static long getLastFactoryResetTimeMs() { 228 return CrashRecoveryProperties.lastFactoryResetTimeMs().orElse(0L); 229 } 230 getMaxRescueLevelAttempted()231 protected static int getMaxRescueLevelAttempted() { 232 return CrashRecoveryProperties.maxRescueLevelAttempted().orElse(LEVEL_NONE); 233 } 234 setFactoryResetProperty(boolean value)235 protected static void setFactoryResetProperty(boolean value) { 236 CrashRecoveryProperties.attemptingFactoryReset(value); 237 } setRebootProperty(boolean value)238 protected static void setRebootProperty(boolean value) { 239 CrashRecoveryProperties.attemptingReboot(value); 240 } 241 setLastFactoryResetTimeMs(long value)242 protected static void setLastFactoryResetTimeMs(long value) { 243 CrashRecoveryProperties.lastFactoryResetTimeMs(value); 244 } 245 setMaxRescueLevelAttempted(int level)246 protected static void setMaxRescueLevelAttempted(int level) { 247 CrashRecoveryProperties.maxRescueLevelAttempted(level); 248 } 249 250 /** 251 * Called when {@code SettingsProvider} has been published, which is a good 252 * opportunity to reset any settings depending on our rescue level. 253 */ onSettingsProviderPublished(Context context)254 public static void onSettingsProviderPublished(Context context) { 255 if (!Flags.deprecateFlagsAndSettingsResets()) { 256 handleNativeRescuePartyResets(); 257 ContentResolver contentResolver = context.getContentResolver(); 258 DeviceConfig.setMonitorCallback( 259 contentResolver, 260 Executors.newSingleThreadExecutor(), 261 new RescuePartyMonitorCallback(context)); 262 } 263 } 264 265 266 /** 267 * Called when {@code RollbackManager} performs Mainline module rollbacks, 268 * to avoid rolled back modules consuming flag values only expected to work 269 * on modules of newer versions. 270 */ resetDeviceConfigForPackages(List<String> packageNames)271 public static void resetDeviceConfigForPackages(List<String> packageNames) { 272 if (!Flags.deprecateFlagsAndSettingsResets()) { 273 if (packageNames == null) { 274 return; 275 } 276 Set<String> namespacesToReset = new ArraySet<String>(); 277 Iterator<String> it = packageNames.iterator(); 278 RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstanceIfCreated(); 279 // Get runtime package to namespace mapping if created. 280 if (rescuePartyObserver != null) { 281 while (it.hasNext()) { 282 String packageName = it.next(); 283 Set<String> runtimeAffectedNamespaces = 284 rescuePartyObserver.getAffectedNamespaceSet(packageName); 285 if (runtimeAffectedNamespaces != null) { 286 namespacesToReset.addAll(runtimeAffectedNamespaces); 287 } 288 } 289 } 290 // Get preset package to namespace mapping if created. 291 Set<String> presetAffectedNamespaces = getPresetNamespacesForPackages( 292 packageNames); 293 if (presetAffectedNamespaces != null) { 294 namespacesToReset.addAll(presetAffectedNamespaces); 295 } 296 297 // Clear flags under the namespaces mapped to these packages. 298 // Using setProperties since DeviceConfig.resetToDefaults bans the current flag set. 299 Iterator<String> namespaceIt = namespacesToReset.iterator(); 300 while (namespaceIt.hasNext()) { 301 String namespaceToReset = namespaceIt.next(); 302 Properties properties = new Properties.Builder(namespaceToReset).build(); 303 try { 304 if (!DeviceConfig.setProperties(properties)) { 305 logCrashRecoveryEvent(Log.ERROR, "Failed to clear properties under " 306 + namespaceToReset 307 + ". Running `device_config get_sync_disabled_for_tests` will confirm" 308 + " if config-bulk-update is enabled."); 309 } 310 } catch (DeviceConfig.BadConfigException exception) { 311 logCrashRecoveryEvent(Log.WARN, "namespace " + namespaceToReset 312 + " is already banned, skip reset."); 313 } 314 } 315 } 316 } 317 getPresetNamespacesForPackages(List<String> packageNames)318 private static Set<String> getPresetNamespacesForPackages(List<String> packageNames) { 319 Set<String> resultSet = new ArraySet<String>(); 320 if (!Flags.deprecateFlagsAndSettingsResets()) { 321 try { 322 String flagVal = DeviceConfig.getString(NAMESPACE_CONFIGURATION, 323 NAMESPACE_TO_PACKAGE_MAPPING_FLAG, ""); 324 String[] mappingEntries = flagVal.split(","); 325 for (int i = 0; i < mappingEntries.length; i++) { 326 if (TextUtils.isEmpty(mappingEntries[i])) { 327 continue; 328 } 329 String[] splitEntry = mappingEntries[i].split(":"); 330 if (splitEntry.length != 2) { 331 throw new RuntimeException("Invalid mapping entry: " + mappingEntries[i]); 332 } 333 String namespace = splitEntry[0]; 334 String packageName = splitEntry[1]; 335 336 if (packageNames.contains(packageName)) { 337 resultSet.add(namespace); 338 } 339 } 340 } catch (Exception e) { 341 resultSet.clear(); 342 Slog.e(TAG, "Failed to read preset package to namespaces mapping.", e); 343 } finally { 344 return resultSet; 345 } 346 } else { 347 return resultSet; 348 } 349 } 350 351 @VisibleForTesting getElapsedRealtime()352 static long getElapsedRealtime() { 353 return SystemClock.elapsedRealtime(); 354 } 355 356 private static class RescuePartyMonitorCallback implements DeviceConfig.MonitorCallback { 357 Context mContext; 358 RescuePartyMonitorCallback(Context context)359 RescuePartyMonitorCallback(Context context) { 360 this.mContext = context; 361 } 362 onNamespaceUpdate(@onNull String updatedNamespace)363 public void onNamespaceUpdate(@NonNull String updatedNamespace) { 364 if (!Flags.deprecateFlagsAndSettingsResets()) { 365 startObservingPackages(mContext, updatedNamespace); 366 } 367 } 368 onDeviceConfigAccess(@onNull String callingPackage, @NonNull String namespace)369 public void onDeviceConfigAccess(@NonNull String callingPackage, 370 @NonNull String namespace) { 371 372 if (!Flags.deprecateFlagsAndSettingsResets()) { 373 RescuePartyObserver.getInstance(mContext).recordDeviceConfigAccess( 374 callingPackage, 375 namespace); 376 } 377 } 378 } 379 startObservingPackages(Context context, @NonNull String updatedNamespace)380 private static void startObservingPackages(Context context, @NonNull String updatedNamespace) { 381 if (!Flags.deprecateFlagsAndSettingsResets()) { 382 RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstance(context); 383 Set<String> callingPackages = rescuePartyObserver.getCallingPackagesSet( 384 updatedNamespace); 385 if (callingPackages == null) { 386 return; 387 } 388 List<String> callingPackageList = new ArrayList<>(); 389 callingPackageList.addAll(callingPackages); 390 Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: " 391 + updatedNamespace); 392 PackageWatchdog.getInstance(context).startExplicitHealthCheck( 393 callingPackageList, 394 DEFAULT_OBSERVING_DURATION_MS, 395 rescuePartyObserver); 396 } 397 } 398 handleNativeRescuePartyResets()399 private static void handleNativeRescuePartyResets() { 400 if (!Flags.deprecateFlagsAndSettingsResets()) { 401 if (SettingsToPropertiesMapper.isNativeFlagsResetPerformed()) { 402 String[] resetNativeCategories = 403 SettingsToPropertiesMapper.getResetNativeCategories(); 404 for (int i = 0; i < resetNativeCategories.length; i++) { 405 // Don't let RescueParty reset the namespace for RescueParty switches. 406 if (NAMESPACE_CONFIGURATION.equals(resetNativeCategories[i])) { 407 continue; 408 } 409 DeviceConfig.resetToDefaults(DEVICE_CONFIG_RESET_MODE, 410 resetNativeCategories[i]); 411 } 412 } 413 } 414 } 415 getMaxRescueLevel(boolean mayPerformReboot)416 private static int getMaxRescueLevel(boolean mayPerformReboot) { 417 if (Flags.recoverabilityDetection()) { 418 if (!mayPerformReboot 419 || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) { 420 return SystemProperties.getInt(RESCUE_NON_REBOOT_LEVEL_LIMIT, 421 DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT); 422 } 423 return RESCUE_LEVEL_FACTORY_RESET; 424 } else { 425 if (!mayPerformReboot 426 || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) { 427 return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS; 428 } 429 return LEVEL_FACTORY_RESET; 430 } 431 } 432 getMaxRescueLevel()433 private static int getMaxRescueLevel() { 434 if (!SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) { 435 return Level.factoryReset(); 436 } 437 return Level.reboot(); 438 } 439 440 /** 441 * Get the rescue level to perform if this is the n-th attempt at mitigating failure. 442 * 443 * @param mitigationCount: the mitigation attempt number (1 = first attempt etc.) 444 * @param mayPerformReboot: whether or not a reboot and factory reset may be performed 445 * for the given failure. 446 * @return the rescue level for the n-th mitigation attempt. 447 */ getRescueLevel(int mitigationCount, boolean mayPerformReboot)448 private static int getRescueLevel(int mitigationCount, boolean mayPerformReboot) { 449 if (!Flags.deprecateFlagsAndSettingsResets()) { 450 if (mitigationCount == 1) { 451 return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS; 452 } else if (mitigationCount == 2) { 453 return LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES; 454 } else if (mitigationCount == 3) { 455 return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS; 456 } else if (mitigationCount == 4) { 457 return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_WARM_REBOOT); 458 } else if (mitigationCount >= 5) { 459 return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_FACTORY_RESET); 460 } else { 461 Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount); 462 return LEVEL_NONE; 463 } 464 } else { 465 if (mitigationCount == 1) { 466 return Level.reboot(); 467 } else if (mitigationCount >= 2) { 468 return Math.min(getMaxRescueLevel(), Level.factoryReset()); 469 } else { 470 Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount); 471 return LEVEL_NONE; 472 } 473 } 474 } 475 476 /** 477 * Get the rescue level to perform if this is the n-th attempt at mitigating failure. 478 * When failedPackage is null then 1st and 2nd mitigation counts are redundant (scoped and 479 * all device config reset). Behaves as if one mitigation attempt was already done. 480 * 481 * @param mitigationCount the mitigation attempt number (1 = first attempt etc.). 482 * @param mayPerformReboot whether or not a reboot and factory reset may be performed 483 * for the given failure. 484 * @param failedPackage in case of bootloop this is null. 485 * @return the rescue level for the n-th mitigation attempt. 486 */ getRescueLevel(int mitigationCount, boolean mayPerformReboot, @Nullable VersionedPackage failedPackage)487 private static @RescueLevels int getRescueLevel(int mitigationCount, boolean mayPerformReboot, 488 @Nullable VersionedPackage failedPackage) { 489 // Skipping RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET since it's not defined without a failed 490 // package. 491 if (failedPackage == null && mitigationCount > 0) { 492 mitigationCount += 1; 493 } 494 if (mitigationCount == 1) { 495 return RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET; 496 } else if (mitigationCount == 2) { 497 return RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET; 498 } else if (mitigationCount == 3) { 499 return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_WARM_REBOOT); 500 } else if (mitigationCount == 4) { 501 return Math.min(getMaxRescueLevel(mayPerformReboot), 502 RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS); 503 } else if (mitigationCount == 5) { 504 return Math.min(getMaxRescueLevel(mayPerformReboot), 505 RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES); 506 } else if (mitigationCount == 6) { 507 return Math.min(getMaxRescueLevel(mayPerformReboot), 508 RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS); 509 } else if (mitigationCount >= 7) { 510 return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_FACTORY_RESET); 511 } else { 512 return RESCUE_LEVEL_NONE; 513 } 514 } 515 516 /** 517 * Get the rescue level to perform if this is the n-th attempt at mitigating failure. 518 * 519 * @param mitigationCount the mitigation attempt number (1 = first attempt etc.). 520 * @return the rescue level for the n-th mitigation attempt. 521 */ getRescueLevel(int mitigationCount)522 private static @RescueLevels int getRescueLevel(int mitigationCount) { 523 if (mitigationCount == 1) { 524 return Level.reboot(); 525 } else if (mitigationCount >= 2) { 526 return Math.min(getMaxRescueLevel(), Level.factoryReset()); 527 } else { 528 return Level.none(); 529 } 530 } 531 executeRescueLevel(Context context, @Nullable String failedPackage, int level)532 private static void executeRescueLevel(Context context, @Nullable String failedPackage, 533 int level) { 534 Slog.w(TAG, "Attempting rescue level " + levelToString(level)); 535 try { 536 executeRescueLevelInternal(context, level, failedPackage); 537 EventLog.writeEvent(LOG_TAG_RESCUE_SUCCESS, level); 538 String successMsg = "Finished rescue level " + levelToString(level); 539 if (!TextUtils.isEmpty(failedPackage)) { 540 successMsg += " for package " + failedPackage; 541 } 542 logCrashRecoveryEvent(Log.DEBUG, successMsg); 543 } catch (Throwable t) { 544 logRescueException(level, failedPackage, t); 545 } 546 } 547 executeRescueLevelInternal(Context context, int level, @Nullable String failedPackage)548 private static void executeRescueLevelInternal(Context context, int level, @Nullable 549 String failedPackage) throws Exception { 550 if (Flags.recoverabilityDetection()) { 551 executeRescueLevelInternalNew(context, level, failedPackage); 552 } else { 553 executeRescueLevelInternalOld(context, level, failedPackage); 554 } 555 } 556 executeRescueLevelInternalOld(Context context, int level, @Nullable String failedPackage)557 private static void executeRescueLevelInternalOld(Context context, int level, @Nullable 558 String failedPackage) throws Exception { 559 CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED, 560 level, levelToString(level)); 561 // Try our best to reset all settings possible, and once finished 562 // rethrow any exception that we encountered 563 Exception res = null; 564 switch (level) { 565 case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: 566 break; 567 case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: 568 break; 569 case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: 570 break; 571 case LEVEL_WARM_REBOOT: 572 executeWarmReboot(context, level, failedPackage); 573 break; 574 case LEVEL_FACTORY_RESET: 575 // Before the completion of Reboot, if any crash happens then PackageWatchdog 576 // escalates to next level i.e. factory reset, as they happen in separate threads. 577 // Adding a check to prevent factory reset to execute before above reboot completes. 578 // Note: this reboot property is not persistent resets after reboot is completed. 579 if (isRebootPropertySet()) { 580 return; 581 } 582 executeFactoryReset(context, level, failedPackage); 583 break; 584 } 585 586 if (res != null) { 587 throw res; 588 } 589 } 590 executeRescueLevelInternalNew(Context context, @RescueLevels int level, @Nullable String failedPackage)591 private static void executeRescueLevelInternalNew(Context context, @RescueLevels int level, 592 @Nullable String failedPackage) throws Exception { 593 CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED, 594 level, levelToString(level)); 595 switch (level) { 596 case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET: 597 break; 598 case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET: 599 break; 600 case RESCUE_LEVEL_WARM_REBOOT: 601 executeWarmReboot(context, level, failedPackage); 602 break; 603 case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: 604 if (!Flags.deprecateFlagsAndSettingsResets()) { 605 resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS, 606 level); 607 } 608 break; 609 case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: 610 if (!Flags.deprecateFlagsAndSettingsResets()) { 611 resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_CHANGES, 612 level); 613 } 614 break; 615 case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: 616 if (!Flags.deprecateFlagsAndSettingsResets()) { 617 resetAllSettingsIfNecessary(context, Settings.RESET_MODE_TRUSTED_DEFAULTS, 618 level); 619 } 620 break; 621 case RESCUE_LEVEL_FACTORY_RESET: 622 // Before the completion of Reboot, if any crash happens then PackageWatchdog 623 // escalates to next level i.e. factory reset, as they happen in separate threads. 624 // Adding a check to prevent factory reset to execute before above reboot completes. 625 // Note: this reboot property is not persistent resets after reboot is completed. 626 if (isRebootPropertySet()) { 627 return; 628 } 629 executeFactoryReset(context, level, failedPackage); 630 break; 631 } 632 } 633 executeWarmReboot(Context context, int level, @Nullable String failedPackage)634 private static void executeWarmReboot(Context context, int level, 635 @Nullable String failedPackage) { 636 if (Flags.deprecateFlagsAndSettingsResets()) { 637 if (shouldThrottleReboot()) { 638 return; 639 } 640 } 641 642 // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog 643 // when device shutting down. 644 setRebootProperty(true); 645 646 if (Flags.synchronousRebootInRescueParty()) { 647 try { 648 PowerManager pm = context.getSystemService(PowerManager.class); 649 if (pm != null) { 650 pm.reboot(TAG); 651 } 652 } catch (Throwable t) { 653 logRescueException(level, failedPackage, t); 654 } 655 } else { 656 Runnable runnable = () -> { 657 try { 658 PowerManager pm = context.getSystemService(PowerManager.class); 659 if (pm != null) { 660 pm.reboot(TAG); 661 } 662 } catch (Throwable t) { 663 logRescueException(level, failedPackage, t); 664 } 665 }; 666 Thread thread = new Thread(runnable); 667 thread.start(); 668 } 669 } 670 executeFactoryReset(Context context, int level, @Nullable String failedPackage)671 private static void executeFactoryReset(Context context, int level, 672 @Nullable String failedPackage) { 673 if (Flags.deprecateFlagsAndSettingsResets()) { 674 if (shouldThrottleReboot()) { 675 return; 676 } 677 } 678 setFactoryResetProperty(true); 679 long now = System.currentTimeMillis(); 680 setLastFactoryResetTimeMs(now); 681 682 if (Flags.synchronousRebootInRescueParty()) { 683 try { 684 RecoverySystem.rebootPromptAndWipeUserData(context, TAG + "," + failedPackage); 685 } catch (Throwable t) { 686 logRescueException(level, failedPackage, t); 687 } 688 } else { 689 Runnable runnable = new Runnable() { 690 @Override 691 public void run() { 692 try { 693 RecoverySystem.rebootPromptAndWipeUserData(context, 694 TAG + "," + failedPackage); 695 } catch (Throwable t) { 696 logRescueException(level, failedPackage, t); 697 } 698 } 699 }; 700 Thread thread = new Thread(runnable); 701 thread.start(); 702 } 703 } 704 705 getCompleteMessage(Throwable t)706 private static String getCompleteMessage(Throwable t) { 707 final StringBuilder builder = new StringBuilder(); 708 builder.append(t.getMessage()); 709 while ((t = t.getCause()) != null) { 710 builder.append(": ").append(t.getMessage()); 711 } 712 return builder.toString(); 713 } 714 logRescueException(int level, @Nullable String failedPackageName, Throwable t)715 private static void logRescueException(int level, @Nullable String failedPackageName, 716 Throwable t) { 717 final String msg = getCompleteMessage(t); 718 EventLog.writeEvent(LOG_TAG_RESCUE_FAILURE, level, msg); 719 String failureMsg = "Failed rescue level " + levelToString(level); 720 if (!TextUtils.isEmpty(failedPackageName)) { 721 failureMsg += " for package " + failedPackageName; 722 } 723 logCrashRecoveryEvent(Log.ERROR, failureMsg + ": " + msg); 724 } 725 mapRescueLevelToUserImpact(int rescueLevel)726 private static int mapRescueLevelToUserImpact(int rescueLevel) { 727 if (Flags.recoverabilityDetection()) { 728 switch (rescueLevel) { 729 case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET: 730 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10; 731 case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET: 732 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_40; 733 case RESCUE_LEVEL_WARM_REBOOT: 734 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50; 735 case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: 736 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_71; 737 case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: 738 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_75; 739 case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: 740 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_80; 741 case RESCUE_LEVEL_FACTORY_RESET: 742 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100; 743 default: 744 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; 745 } 746 } else { 747 switch (rescueLevel) { 748 case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: 749 case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: 750 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10; 751 case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: 752 case LEVEL_WARM_REBOOT: 753 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50; 754 case LEVEL_FACTORY_RESET: 755 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100; 756 default: 757 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; 758 } 759 } 760 } 761 resetAllSettingsIfNecessary(Context context, int mode, int level)762 private static void resetAllSettingsIfNecessary(Context context, int mode, 763 int level) throws Exception { 764 if (!Flags.deprecateFlagsAndSettingsResets()) { 765 // No need to reset Settings again if they are already reset in the current level once. 766 if (getMaxRescueLevelAttempted() >= level) { 767 return; 768 } 769 setMaxRescueLevelAttempted(level); 770 // Try our best to reset all settings possible, and once finished 771 // rethrow any exception that we encountered 772 Exception res = null; 773 final ContentResolver resolver = context.getContentResolver(); 774 try { 775 Settings.Global.resetToDefaultsAsUser(resolver, null, mode, 776 UserHandle.SYSTEM.getIdentifier()); 777 } catch (Exception e) { 778 res = new RuntimeException("Failed to reset global settings", e); 779 } 780 for (int userId : getAllUserIds()) { 781 try { 782 Settings.Secure.resetToDefaultsAsUser(resolver, null, mode, userId); 783 } catch (Exception e) { 784 res = new RuntimeException("Failed to reset secure settings for " + userId, e); 785 } 786 } 787 if (res != null) { 788 throw res; 789 } 790 } 791 } 792 793 /** 794 * Handle mitigation action for package failures. This observer will be register to Package 795 * Watchdog and will receive calls about package failures. This observer is persistent so it 796 * may choose to mitigate failures for packages it has not explicitly asked to observe. 797 */ 798 public static class RescuePartyObserver implements PackageHealthObserver { 799 800 private final Context mContext; 801 private final Map<String, Set<String>> mCallingPackageNamespaceSetMap = new HashMap<>(); 802 private final Map<String, Set<String>> mNamespaceCallingPackageSetMap = new HashMap<>(); 803 804 @GuardedBy("RescuePartyObserver.class") 805 static RescuePartyObserver sRescuePartyObserver; 806 RescuePartyObserver(Context context)807 private RescuePartyObserver(Context context) { 808 mContext = context; 809 } 810 811 /** Creates or gets singleton instance of RescueParty. */ getInstance(Context context)812 public static RescuePartyObserver getInstance(Context context) { 813 synchronized (RescuePartyObserver.class) { 814 if (sRescuePartyObserver == null) { 815 sRescuePartyObserver = new RescuePartyObserver(context); 816 } 817 return sRescuePartyObserver; 818 } 819 } 820 821 /** Gets singleton instance. It returns null if the instance is not created yet.*/ 822 @Nullable getInstanceIfCreated()823 public static RescuePartyObserver getInstanceIfCreated() { 824 synchronized (RescuePartyObserver.class) { 825 return sRescuePartyObserver; 826 } 827 } 828 829 @VisibleForTesting reset()830 static void reset() { 831 synchronized (RescuePartyObserver.class) { 832 sRescuePartyObserver = null; 833 } 834 } 835 836 @Override onHealthCheckFailed(@ullable VersionedPackage failedPackage, @FailureReasons int failureReason, int mitigationCount)837 public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage, 838 @FailureReasons int failureReason, int mitigationCount) { 839 int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; 840 if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH 841 || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) { 842 if (Flags.recoverabilityDetection()) { 843 if (!Flags.deprecateFlagsAndSettingsResets()) { 844 impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, 845 mayPerformReboot(failedPackage), failedPackage)); 846 } else { 847 impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount)); 848 } 849 } else { 850 impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, 851 mayPerformReboot(failedPackage))); 852 } 853 } 854 855 Slog.i(TAG, "Checking available remediations for health check failure." 856 + " failedPackage: " 857 + (failedPackage == null ? null : failedPackage.getPackageName()) 858 + " failureReason: " + failureReason 859 + " available impact: " + impact); 860 return impact; 861 } 862 863 @Override onExecuteHealthCheckMitigation(@ullable VersionedPackage failedPackage, @FailureReasons int failureReason, int mitigationCount)864 public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage, 865 @FailureReasons int failureReason, int mitigationCount) { 866 if (isDisabled()) { 867 return MITIGATION_RESULT_SKIPPED; 868 } 869 Slog.i(TAG, "Executing remediation." 870 + " failedPackage: " 871 + (failedPackage == null ? null : failedPackage.getPackageName()) 872 + " failureReason: " + failureReason 873 + " mitigationCount: " + mitigationCount); 874 if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH 875 || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) { 876 final int level; 877 if (Flags.recoverabilityDetection()) { 878 if (!Flags.deprecateFlagsAndSettingsResets()) { 879 level = getRescueLevel(mitigationCount, mayPerformReboot(failedPackage), 880 failedPackage); 881 } else { 882 level = getRescueLevel(mitigationCount); 883 } 884 } else { 885 level = getRescueLevel(mitigationCount, mayPerformReboot(failedPackage)); 886 } 887 executeRescueLevel(mContext, 888 failedPackage == null ? null : failedPackage.getPackageName(), level); 889 return MITIGATION_RESULT_SUCCESS; 890 } else { 891 return MITIGATION_RESULT_SKIPPED; 892 } 893 } 894 895 @Override isPersistent()896 public boolean isPersistent() { 897 return true; 898 } 899 900 @Override mayObservePackage(String packageName)901 public boolean mayObservePackage(String packageName) { 902 PackageManager pm = mContext.getPackageManager(); 903 try { 904 // A package is a module if this is non-null 905 if (pm.getModuleInfo(packageName, 0) != null) { 906 return true; 907 } 908 } catch (PackageManager.NameNotFoundException | IllegalStateException ignore) { 909 } 910 911 return isPersistentSystemApp(packageName); 912 } 913 914 @Override onBootLoop(int mitigationCount)915 public int onBootLoop(int mitigationCount) { 916 if (isDisabled()) { 917 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; 918 } 919 if (Flags.recoverabilityDetection()) { 920 if (!Flags.deprecateFlagsAndSettingsResets()) { 921 return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, 922 true, /*failedPackage=*/ null)); 923 } else { 924 return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount)); 925 } 926 } else { 927 return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true)); 928 } 929 } 930 931 @Override onExecuteBootLoopMitigation(int mitigationCount)932 public int onExecuteBootLoopMitigation(int mitigationCount) { 933 if (isDisabled()) { 934 return MITIGATION_RESULT_SKIPPED; 935 } 936 boolean mayPerformReboot = !shouldThrottleReboot(); 937 final int level; 938 if (Flags.recoverabilityDetection()) { 939 if (!Flags.deprecateFlagsAndSettingsResets()) { 940 level = getRescueLevel(mitigationCount, mayPerformReboot, 941 /*failedPackage=*/ null); 942 } else { 943 level = getRescueLevel(mitigationCount); 944 } 945 } else { 946 level = getRescueLevel(mitigationCount, mayPerformReboot); 947 } 948 executeRescueLevel(mContext, /*failedPackage=*/ null, level); 949 return MITIGATION_RESULT_SUCCESS; 950 } 951 952 @Override getUniqueIdentifier()953 public String getUniqueIdentifier() { 954 return NAME; 955 } 956 957 /** 958 * Returns {@code true} if the failing package is non-null and performing a reboot or 959 * prompting a factory reset is an acceptable mitigation strategy for the package's 960 * failure, {@code false} otherwise. 961 */ mayPerformReboot(@ullable VersionedPackage failingPackage)962 private boolean mayPerformReboot(@Nullable VersionedPackage failingPackage) { 963 if (failingPackage == null) { 964 return false; 965 } 966 if (shouldThrottleReboot()) { 967 return false; 968 } 969 970 return isPersistentSystemApp(failingPackage.getPackageName()); 971 } 972 isPersistentSystemApp(@onNull String packageName)973 private boolean isPersistentSystemApp(@NonNull String packageName) { 974 PackageManager pm = mContext.getPackageManager(); 975 try { 976 ApplicationInfo info = pm.getApplicationInfo(packageName, 0); 977 return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK; 978 } catch (PackageManager.NameNotFoundException e) { 979 return false; 980 } 981 } 982 recordDeviceConfigAccess(@onNull String callingPackage, @NonNull String namespace)983 private synchronized void recordDeviceConfigAccess(@NonNull String callingPackage, 984 @NonNull String namespace) { 985 if (!Flags.deprecateFlagsAndSettingsResets()) { 986 // Record it in calling packages to namespace map 987 Set<String> namespaceSet = mCallingPackageNamespaceSetMap.get(callingPackage); 988 if (namespaceSet == null) { 989 namespaceSet = new ArraySet<>(); 990 mCallingPackageNamespaceSetMap.put(callingPackage, namespaceSet); 991 } 992 namespaceSet.add(namespace); 993 // Record it in namespace to calling packages map 994 Set<String> callingPackageSet = mNamespaceCallingPackageSetMap.get(namespace); 995 if (callingPackageSet == null) { 996 callingPackageSet = new ArraySet<>(); 997 } 998 callingPackageSet.add(callingPackage); 999 mNamespaceCallingPackageSetMap.put(namespace, callingPackageSet); 1000 } 1001 } 1002 getAffectedNamespaceSet(String failedPackage)1003 private synchronized Set<String> getAffectedNamespaceSet(String failedPackage) { 1004 return mCallingPackageNamespaceSetMap.get(failedPackage); 1005 } 1006 getAllAffectedNamespaceSet()1007 private synchronized Set<String> getAllAffectedNamespaceSet() { 1008 return new HashSet<String>(mNamespaceCallingPackageSetMap.keySet()); 1009 } 1010 getCallingPackagesSet(String namespace)1011 private synchronized Set<String> getCallingPackagesSet(String namespace) { 1012 return mNamespaceCallingPackageSetMap.get(namespace); 1013 } 1014 } 1015 1016 /** 1017 * Returns {@code true} if Rescue Party is allowed to attempt a reboot or factory reset. 1018 * Will return {@code false} if a factory reset was already offered recently. 1019 */ shouldThrottleReboot()1020 private static boolean shouldThrottleReboot() { 1021 Long lastResetTime = getLastFactoryResetTimeMs(); 1022 long now = System.currentTimeMillis(); 1023 long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG, 1024 DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN); 1025 return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin); 1026 } 1027 getAllUserIds()1028 private static int[] getAllUserIds() { 1029 int systemUserId = UserHandle.SYSTEM.getIdentifier(); 1030 int[] userIds = { systemUserId }; 1031 try { 1032 for (File file : FileUtils.listFilesOrEmpty(Environment.getDataSystemDeDirectory())) { 1033 try { 1034 final int userId = Integer.parseInt(file.getName()); 1035 if (userId != systemUserId) { 1036 userIds = ArrayUtils.appendInt(userIds, userId); 1037 } 1038 } catch (NumberFormatException ignored) { 1039 } 1040 } 1041 } catch (Throwable t) { 1042 Slog.w(TAG, "Trouble discovering users", t); 1043 } 1044 return userIds; 1045 } 1046 1047 /** 1048 * Hacky test to check if the device has an active USB connection, which is 1049 * a good proxy for someone doing local development work. 1050 */ isUsbActive()1051 private static boolean isUsbActive() { 1052 if (SystemProperties.getBoolean(PROP_VIRTUAL_DEVICE, false)) { 1053 Slog.v(TAG, "Assuming virtual device is connected over USB"); 1054 return true; 1055 } 1056 try { 1057 final String state = FileUtils 1058 .readTextFile(new File("/sys/class/android_usb/android0/state"), 128, ""); 1059 return "CONFIGURED".equals(state.trim()); 1060 } catch (Throwable t) { 1061 Slog.w(TAG, "Failed to determine if device was on USB", t); 1062 return false; 1063 } 1064 } 1065 1066 private static class Level { none()1067 static int none() { 1068 return Flags.recoverabilityDetection() ? RESCUE_LEVEL_NONE : LEVEL_NONE; 1069 } 1070 reboot()1071 static int reboot() { 1072 return Flags.recoverabilityDetection() ? RESCUE_LEVEL_WARM_REBOOT : LEVEL_WARM_REBOOT; 1073 } 1074 factoryReset()1075 static int factoryReset() { 1076 return Flags.recoverabilityDetection() 1077 ? RESCUE_LEVEL_FACTORY_RESET 1078 : LEVEL_FACTORY_RESET; 1079 } 1080 } 1081 levelToString(int level)1082 private static String levelToString(int level) { 1083 if (Flags.recoverabilityDetection()) { 1084 switch (level) { 1085 case RESCUE_LEVEL_NONE: 1086 return "NONE"; 1087 case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET: 1088 return "SCOPED_DEVICE_CONFIG_RESET"; 1089 case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET: 1090 return "ALL_DEVICE_CONFIG_RESET"; 1091 case RESCUE_LEVEL_WARM_REBOOT: 1092 return "WARM_REBOOT"; 1093 case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: 1094 return "RESET_SETTINGS_UNTRUSTED_DEFAULTS"; 1095 case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: 1096 return "RESET_SETTINGS_UNTRUSTED_CHANGES"; 1097 case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: 1098 return "RESET_SETTINGS_TRUSTED_DEFAULTS"; 1099 case RESCUE_LEVEL_FACTORY_RESET: 1100 return "FACTORY_RESET"; 1101 default: 1102 return Integer.toString(level); 1103 } 1104 } else { 1105 switch (level) { 1106 case LEVEL_NONE: 1107 return "NONE"; 1108 case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: 1109 return "RESET_SETTINGS_UNTRUSTED_DEFAULTS"; 1110 case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: 1111 return "RESET_SETTINGS_UNTRUSTED_CHANGES"; 1112 case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: 1113 return "RESET_SETTINGS_TRUSTED_DEFAULTS"; 1114 case LEVEL_WARM_REBOOT: 1115 return "WARM_REBOOT"; 1116 case LEVEL_FACTORY_RESET: 1117 return "FACTORY_RESET"; 1118 default: 1119 return Integer.toString(level); 1120 } 1121 } 1122 } 1123 } 1124