• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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