• 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 com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
20 import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
21 import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
22 
23 import android.annotation.IntDef;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.content.Context;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.VersionedPackage;
30 import android.crashrecovery.flags.Flags;
31 import android.os.Build;
32 import android.os.PowerManager;
33 import android.os.RecoverySystem;
34 import android.os.SystemClock;
35 import android.os.SystemProperties;
36 import android.sysprop.CrashRecoveryProperties;
37 import android.text.TextUtils;
38 import android.util.EventLog;
39 import android.util.FileUtils;
40 import android.util.Log;
41 import android.util.Slog;
42 
43 import com.android.internal.annotations.GuardedBy;
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.server.PackageWatchdog.FailureReasons;
46 import com.android.server.PackageWatchdog.PackageHealthObserver;
47 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
48 import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
49 
50 import java.io.File;
51 import java.lang.annotation.Retention;
52 import java.lang.annotation.RetentionPolicy;
53 import java.util.concurrent.TimeUnit;
54 
55 /**
56  * Utilities to help rescue the system from crash loops. Callers are expected to
57  * report boot events and persistent app crashes, and if they happen frequently
58  * enough this class will slowly escalate through several rescue operations
59  * before finally rebooting and prompting the user if they want to wipe data as
60  * a last resort.
61  *
62  * @hide
63  */
64 public class RescueParty {
65     @VisibleForTesting
66     static final String PROP_ENABLE_RESCUE = "persist.sys.enable_rescue";
67     @VisibleForTesting
68     static final int LEVEL_FACTORY_RESET = 5;
69     @VisibleForTesting
70     static final int RESCUE_LEVEL_NONE = 0;
71     @VisibleForTesting
72     static final int RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET = 1;
73     @VisibleForTesting
74     static final int RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET = 2;
75     @VisibleForTesting
76     static final int RESCUE_LEVEL_WARM_REBOOT = 3;
77     @VisibleForTesting
78     static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 4;
79     @VisibleForTesting
80     static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 5;
81     @VisibleForTesting
82     static final int RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 6;
83     @VisibleForTesting
84     static final int RESCUE_LEVEL_FACTORY_RESET = 7;
85 
86     @IntDef(prefix = { "RESCUE_LEVEL_" }, value = {
87         RESCUE_LEVEL_NONE,
88         RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET,
89         RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET,
90         RESCUE_LEVEL_WARM_REBOOT,
91         RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
92         RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
93         RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
94         RESCUE_LEVEL_FACTORY_RESET
95     })
96     @Retention(RetentionPolicy.SOURCE)
97     @interface RescueLevels {}
98 
99     @VisibleForTesting
100     static final String TAG = "RescueParty";
101     @VisibleForTesting
102     static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 1440;
103 
104     private static final String NAME = "rescue-party-observer";
105 
106     private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
107     private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
108     private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
109             "persist.device_config.configuration.disable_rescue_party";
110     private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
111             "persist.device_config.configuration.disable_rescue_party_factory_reset";
112     private static final String PROP_THROTTLE_DURATION_MIN_FLAG =
113             "persist.device_config.configuration.rescue_party_throttle_duration_min";
114 
115     private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
116             | ApplicationInfo.FLAG_SYSTEM;
117 
118     /**
119      * EventLog tags used when logging into the event log. Note the values must be sync with
120      * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
121      * name translation.
122      */
123     private static final int LOG_TAG_RESCUE_SUCCESS = 2902;
124     private static final int LOG_TAG_RESCUE_FAILURE = 2903;
125 
126     /** Register the Rescue Party observer as a Package Watchdog health observer */
registerHealthObserver(Context context)127     public static void registerHealthObserver(Context context) {
128         PackageWatchdog.getInstance(context).registerHealthObserver(
129                 context.getMainExecutor(), RescuePartyObserver.getInstance(context));
130     }
131 
isDisabled()132     private static boolean isDisabled() {
133         // Check if we're explicitly enabled for testing
134         if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) {
135             return false;
136         }
137 
138         // We're disabled if the DeviceConfig disable flag is set to true.
139         // This is in case that an emergency rollback of the feature is needed.
140         if (SystemProperties.getBoolean(PROP_DEVICE_CONFIG_DISABLE_FLAG, false)) {
141             Slog.v(TAG, "Disabled because of DeviceConfig flag");
142             return true;
143         }
144 
145         // We're disabled on all engineering devices
146         if (Build.TYPE.equals("eng")) {
147             Slog.v(TAG, "Disabled because of eng build");
148             return true;
149         }
150 
151         // We're disabled on userdebug devices connected over USB, since that's
152         // a decent signal that someone is actively trying to debug the device,
153         // or that it's in a lab environment.
154         if (Build.TYPE.equals("userdebug") && isUsbActive()) {
155             Slog.v(TAG, "Disabled because of active USB connection");
156             return true;
157         }
158 
159         // One last-ditch check
160         if (SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false)) {
161             Slog.v(TAG, "Disabled because of manual property");
162             return true;
163         }
164 
165         return false;
166     }
167 
168     /**
169      * Check if we're currently attempting to reboot for a factory reset. This method must
170      * return true if RescueParty tries to reboot early during a boot loop, since the device
171      * will not be fully booted at this time.
172      */
isRecoveryTriggeredReboot()173     public static boolean isRecoveryTriggeredReboot() {
174         return isFactoryResetPropertySet() || isRebootPropertySet();
175     }
176 
isFactoryResetPropertySet()177     static boolean isFactoryResetPropertySet() {
178         return CrashRecoveryProperties.attemptingFactoryReset().orElse(false);
179     }
180 
isRebootPropertySet()181     static boolean isRebootPropertySet() {
182         return CrashRecoveryProperties.attemptingReboot().orElse(false);
183     }
184 
getLastFactoryResetTimeMs()185     protected static long getLastFactoryResetTimeMs() {
186         return CrashRecoveryProperties.lastFactoryResetTimeMs().orElse(0L);
187     }
188 
getMaxRescueLevelAttempted()189     protected static int getMaxRescueLevelAttempted() {
190         return CrashRecoveryProperties.maxRescueLevelAttempted().orElse(RESCUE_LEVEL_NONE);
191     }
192 
setFactoryResetProperty(boolean value)193     protected static void setFactoryResetProperty(boolean value) {
194         CrashRecoveryProperties.attemptingFactoryReset(value);
195     }
setRebootProperty(boolean value)196     protected static void setRebootProperty(boolean value) {
197         CrashRecoveryProperties.attemptingReboot(value);
198     }
199 
setLastFactoryResetTimeMs(long value)200     protected static void setLastFactoryResetTimeMs(long value) {
201         CrashRecoveryProperties.lastFactoryResetTimeMs(value);
202     }
203 
setMaxRescueLevelAttempted(int level)204     protected static void setMaxRescueLevelAttempted(int level) {
205         CrashRecoveryProperties.maxRescueLevelAttempted(level);
206     }
207 
208     @VisibleForTesting
getElapsedRealtime()209     static long getElapsedRealtime() {
210         return SystemClock.elapsedRealtime();
211     }
212 
getMaxRescueLevel()213     private static int getMaxRescueLevel() {
214         if (!SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
215             return Level.factoryReset();
216         }
217         return Level.reboot();
218     }
219 
220     /**
221      * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
222      *
223      * @param mitigationCount the mitigation attempt number (1 = first attempt etc.).
224      * @return the rescue level for the n-th mitigation attempt.
225      */
getRescueLevel(int mitigationCount)226     private static @RescueLevels int getRescueLevel(int mitigationCount) {
227         if (mitigationCount == 1) {
228             return Level.reboot();
229         } else if (mitigationCount >= 2) {
230             return Math.min(getMaxRescueLevel(), Level.factoryReset());
231         } else {
232             return Level.none();
233         }
234     }
235 
executeRescueLevel(Context context, @Nullable String failedPackage, int level)236     private static void executeRescueLevel(Context context, @Nullable String failedPackage,
237             int level) {
238         Slog.w(TAG, "Attempting rescue level " + levelToString(level));
239         try {
240             executeRescueLevelInternal(context, level, failedPackage);
241             EventLog.writeEvent(LOG_TAG_RESCUE_SUCCESS, level);
242             String successMsg = "Finished rescue level " + levelToString(level);
243             if (!TextUtils.isEmpty(failedPackage)) {
244                 successMsg += " for package " + failedPackage;
245             }
246             logCrashRecoveryEvent(Log.DEBUG, successMsg);
247         } catch (Throwable t) {
248             logRescueException(level, failedPackage, t);
249         }
250     }
251 
executeRescueLevelInternal(Context context, @RescueLevels int level, @Nullable String failedPackage)252     private static void executeRescueLevelInternal(Context context, @RescueLevels int level,
253             @Nullable String failedPackage) {
254         CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED,
255                 level, levelToString(level));
256         switch (level) {
257             case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
258                 break;
259             case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
260                 break;
261             case RESCUE_LEVEL_WARM_REBOOT:
262                 executeWarmReboot(context, level, failedPackage);
263                 break;
264             case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
265                 // do nothing
266                 break;
267             case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
268                 // do nothing
269                 break;
270             case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
271                 // do nothing
272                 break;
273             case RESCUE_LEVEL_FACTORY_RESET:
274                 // Before the completion of Reboot, if any crash happens then PackageWatchdog
275                 // escalates to next level i.e. factory reset, as they happen in separate threads.
276                 // Adding a check to prevent factory reset to execute before above reboot completes.
277                 // Note: this reboot property is not persistent resets after reboot is completed.
278                 if (isRebootPropertySet()) {
279                     return;
280                 }
281                 executeFactoryReset(context, level, failedPackage);
282                 break;
283         }
284     }
285 
executeWarmReboot(Context context, int level, @Nullable String failedPackage)286     private static void executeWarmReboot(Context context, int level,
287             @Nullable String failedPackage) {
288         if (shouldThrottleReboot()) {
289             return;
290         }
291 
292         // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog
293         // when device shutting down.
294         setRebootProperty(true);
295 
296         if (Flags.synchronousRebootInRescueParty()) {
297             try {
298                 PowerManager pm = context.getSystemService(PowerManager.class);
299                 if (pm != null) {
300                     pm.reboot(TAG);
301                 }
302             } catch (Throwable t) {
303                 logRescueException(level, failedPackage, t);
304             }
305         } else {
306             Runnable runnable = () -> {
307                 try {
308                     PowerManager pm = context.getSystemService(PowerManager.class);
309                     if (pm != null) {
310                         pm.reboot(TAG);
311                     }
312                 } catch (Throwable t) {
313                     logRescueException(level, failedPackage, t);
314                 }
315             };
316             Thread thread = new Thread(runnable);
317             thread.start();
318         }
319     }
320 
executeFactoryReset(Context context, int level, @Nullable String failedPackage)321     private static void executeFactoryReset(Context context, int level,
322             @Nullable String failedPackage) {
323         if (shouldThrottleReboot()) {
324             return;
325         }
326         setFactoryResetProperty(true);
327         long now = System.currentTimeMillis();
328         setLastFactoryResetTimeMs(now);
329 
330         if (Flags.synchronousRebootInRescueParty()) {
331             try {
332                 RecoverySystem.rebootPromptAndWipeUserData(context, TAG + "," + failedPackage);
333             } catch (Throwable t) {
334                 logRescueException(level, failedPackage, t);
335             }
336         } else {
337             Runnable runnable = new Runnable() {
338                 @Override
339                 public void run() {
340                     try {
341                         RecoverySystem.rebootPromptAndWipeUserData(context,
342                             TAG + "," + failedPackage);
343                     } catch (Throwable t) {
344                         logRescueException(level, failedPackage, t);
345                     }
346                 }
347             };
348             Thread thread = new Thread(runnable);
349             thread.start();
350         }
351     }
352 
353 
getCompleteMessage(Throwable t)354     private static String getCompleteMessage(Throwable t) {
355         final StringBuilder builder = new StringBuilder();
356         builder.append(t.getMessage());
357         while ((t = t.getCause()) != null) {
358             builder.append(": ").append(t.getMessage());
359         }
360         return builder.toString();
361     }
362 
logRescueException(int level, @Nullable String failedPackageName, Throwable t)363     private static void logRescueException(int level, @Nullable String failedPackageName,
364             Throwable t) {
365         final String msg = getCompleteMessage(t);
366         EventLog.writeEvent(LOG_TAG_RESCUE_FAILURE, level, msg);
367         String failureMsg = "Failed rescue level " + levelToString(level);
368         if (!TextUtils.isEmpty(failedPackageName)) {
369             failureMsg += " for package " + failedPackageName;
370         }
371         logCrashRecoveryEvent(Log.ERROR, failureMsg + ": " + msg);
372     }
373 
mapRescueLevelToUserImpact(int rescueLevel)374     private static int mapRescueLevelToUserImpact(int rescueLevel) {
375         switch (rescueLevel) {
376             case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
377                 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
378             case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
379                 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_40;
380             case RESCUE_LEVEL_WARM_REBOOT:
381                 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
382             case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
383                 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
384             case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
385                 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_75;
386             case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
387                 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_80;
388             case RESCUE_LEVEL_FACTORY_RESET:
389                 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
390             default:
391                 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
392         }
393     }
394 
395     /**
396      * Handle mitigation action for package failures. This observer will be register to Package
397      * Watchdog and will receive calls about package failures. This observer is persistent so it
398      * may choose to mitigate failures for packages it has not explicitly asked to observe.
399      */
400     public static class RescuePartyObserver implements PackageHealthObserver {
401 
402         private final Context mContext;
403 
404         @GuardedBy("RescuePartyObserver.class")
405         static RescuePartyObserver sRescuePartyObserver;
406 
RescuePartyObserver(Context context)407         private RescuePartyObserver(Context context) {
408             mContext = context;
409         }
410 
411         /** Creates or gets singleton instance of RescueParty. */
getInstance(Context context)412         public static RescuePartyObserver getInstance(Context context) {
413             synchronized (RescuePartyObserver.class) {
414                 if (sRescuePartyObserver == null) {
415                     sRescuePartyObserver = new RescuePartyObserver(context);
416                 }
417                 return sRescuePartyObserver;
418             }
419         }
420 
421         @VisibleForTesting
reset()422         static void reset() {
423             synchronized (RescuePartyObserver.class) {
424                 sRescuePartyObserver = null;
425             }
426         }
427 
428         @Override
onHealthCheckFailed(@ullable VersionedPackage failedPackage, @FailureReasons int failureReason, int mitigationCount)429         public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
430                 @FailureReasons int failureReason, int mitigationCount) {
431             int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
432             if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
433                     || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
434                 impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
435             }
436 
437             Slog.i(TAG, "Checking available remediations for health check failure."
438                     + " failedPackage: "
439                     + (failedPackage == null ? null : failedPackage.getPackageName())
440                     + " failureReason: " + failureReason
441                     + " available impact: " + impact);
442             return impact;
443         }
444 
445         @Override
onExecuteHealthCheckMitigation(@ullable VersionedPackage failedPackage, @FailureReasons int failureReason, int mitigationCount)446         public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
447                 @FailureReasons int failureReason, int mitigationCount) {
448             if (isDisabled()) {
449                 return MITIGATION_RESULT_SKIPPED;
450             }
451             Slog.i(TAG, "Executing remediation."
452                     + " failedPackage: "
453                     + (failedPackage == null ? null : failedPackage.getPackageName())
454                     + " failureReason: " + failureReason
455                     + " mitigationCount: " + mitigationCount);
456             if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
457                     || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
458                 final int level;
459                 level = getRescueLevel(mitigationCount);
460                 executeRescueLevel(mContext,
461                         failedPackage == null ? null : failedPackage.getPackageName(), level);
462                 return MITIGATION_RESULT_SUCCESS;
463             } else {
464                 return MITIGATION_RESULT_SKIPPED;
465             }
466         }
467 
468         @Override
isPersistent()469         public boolean isPersistent() {
470             return true;
471         }
472 
473         @Override
mayObservePackage(@onNull String packageName)474         public boolean mayObservePackage(@NonNull String packageName) {
475             PackageManager pm = mContext.getPackageManager();
476             try {
477                 // A package is a module if this is non-null
478                 if (pm.getModuleInfo(packageName, 0) != null) {
479                     return true;
480                 }
481             } catch (PackageManager.NameNotFoundException | IllegalStateException ignore) {
482             }
483 
484             return isPersistentSystemApp(packageName);
485         }
486 
487         @Override
onBootLoop(int mitigationCount)488         public int onBootLoop(int mitigationCount) {
489             if (isDisabled()) {
490                 return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
491             }
492             return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
493         }
494 
495         @Override
onExecuteBootLoopMitigation(int mitigationCount)496         public int onExecuteBootLoopMitigation(int mitigationCount) {
497             if (isDisabled()) {
498                 return MITIGATION_RESULT_SKIPPED;
499             }
500             final int level;
501             level = getRescueLevel(mitigationCount);
502             executeRescueLevel(mContext, /*failedPackage=*/ null, level);
503             return MITIGATION_RESULT_SUCCESS;
504         }
505 
506         @Override
getUniqueIdentifier()507         public String getUniqueIdentifier() {
508             return NAME;
509         }
510 
isPersistentSystemApp(@onNull String packageName)511         private boolean isPersistentSystemApp(@NonNull String packageName) {
512             PackageManager pm = mContext.getPackageManager();
513             try {
514                 ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
515                 return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
516             } catch (PackageManager.NameNotFoundException e) {
517                 return false;
518             }
519         }
520 
521     }
522 
523     /**
524      * Returns {@code true} if Rescue Party is allowed to attempt a reboot or factory reset.
525      * Will return {@code false} if a factory reset was already offered recently.
526      */
shouldThrottleReboot()527     private static boolean shouldThrottleReboot() {
528         Long lastResetTime = getLastFactoryResetTimeMs();
529         long now = System.currentTimeMillis();
530         long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG,
531                 DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN);
532         return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin);
533     }
534 
535     /**
536      * Hacky test to check if the device has an active USB connection, which is
537      * a good proxy for someone doing local development work.
538      */
isUsbActive()539     private static boolean isUsbActive() {
540         if (SystemProperties.getBoolean(PROP_VIRTUAL_DEVICE, false)) {
541             Slog.v(TAG, "Assuming virtual device is connected over USB");
542             return true;
543         }
544         try {
545             final String state = FileUtils
546                     .readTextFile(new File("/sys/class/android_usb/android0/state"), 128, "");
547             return "CONFIGURED".equals(state.trim());
548         } catch (Throwable t) {
549             Slog.w(TAG, "Failed to determine if device was on USB", t);
550             return false;
551         }
552     }
553 
554     private static class Level {
none()555         static int none() {
556             return RESCUE_LEVEL_NONE;
557         }
558 
reboot()559         static int reboot() {
560             return RESCUE_LEVEL_WARM_REBOOT;
561         }
562 
factoryReset()563         static int factoryReset() {
564             return RESCUE_LEVEL_FACTORY_RESET;
565         }
566     }
567 
levelToString(int level)568     private static String levelToString(int level) {
569         switch (level) {
570             case RESCUE_LEVEL_NONE:
571                 return "NONE";
572             case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
573                 return "SCOPED_DEVICE_CONFIG_RESET";
574             case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
575                 return "ALL_DEVICE_CONFIG_RESET";
576             case RESCUE_LEVEL_WARM_REBOOT:
577                 return "WARM_REBOOT";
578             case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
579                 return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
580             case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
581                 return "RESET_SETTINGS_UNTRUSTED_CHANGES";
582             case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
583                 return "RESET_SETTINGS_TRUSTED_DEFAULTS";
584             case RESCUE_LEVEL_FACTORY_RESET:
585                 return "FACTORY_RESET";
586             default:
587                 return Integer.toString(level);
588         }
589     }
590 }
591