1 /*
2  * Copyright (C) 2008 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 
18 package com.android.server.power;
19 
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.app.IActivityManager;
23 import android.app.ProgressDialog;
24 import android.app.admin.SecurityLog;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.DialogInterface;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.media.AudioAttributes;
31 import android.os.FileUtils;
32 import android.os.Handler;
33 import android.os.PowerManager;
34 import android.os.RecoverySystem;
35 import android.os.RemoteException;
36 import android.os.ServiceManager;
37 import android.os.SystemClock;
38 import android.os.SystemProperties;
39 import android.os.SystemVibrator;
40 import android.os.Trace;
41 import android.os.UserHandle;
42 import android.os.UserManager;
43 import android.os.Vibrator;
44 import android.telephony.TelephonyManager;
45 import android.util.ArrayMap;
46 import android.util.Log;
47 import android.util.Slog;
48 import android.util.TimingsTraceLog;
49 import android.view.WindowManager;
50 
51 import com.android.server.LocalServices;
52 import com.android.server.RescueParty;
53 import com.android.server.pm.PackageManagerService;
54 import com.android.server.statusbar.StatusBarManagerInternal;
55 
56 import java.io.File;
57 import java.io.FileOutputStream;
58 import java.io.IOException;
59 import java.nio.charset.StandardCharsets;
60 
61 public final class ShutdownThread extends Thread {
62     // constants
63     private static final String TAG = "ShutdownThread";
64     private static final int ACTION_DONE_POLL_WAIT_MS = 500;
65     private static final int RADIOS_STATE_POLL_SLEEP_MS = 100;
66     // maximum time we wait for the shutdown broadcast before going on.
67     private static final int MAX_BROADCAST_TIME = 10*1000;
68     private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
69     private static final int MAX_RADIO_WAIT_TIME = 12*1000;
70     private static final int MAX_UNCRYPT_WAIT_TIME = 15*60*1000;
71     // constants for progress bar. the values are roughly estimated based on timeout.
72     private static final int BROADCAST_STOP_PERCENT = 2;
73     private static final int ACTIVITY_MANAGER_STOP_PERCENT = 4;
74     private static final int PACKAGE_MANAGER_STOP_PERCENT = 6;
75     private static final int RADIO_STOP_PERCENT = 18;
76     private static final int MOUNT_SERVICE_STOP_PERCENT = 20;
77 
78     // length of vibration before shutting down
79     private static final int SHUTDOWN_VIBRATE_MS = 500;
80 
81     // state tracking
82     private static final Object sIsStartedGuard = new Object();
83     private static boolean sIsStarted = false;
84 
85     private static boolean mReboot;
86     private static boolean mRebootSafeMode;
87     private static boolean mRebootHasProgressBar;
88     private static String mReason;
89 
90     // Provides shutdown assurance in case the system_server is killed
91     public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested";
92 
93     // Indicates whether we are rebooting into safe mode
94     public static final String REBOOT_SAFEMODE_PROPERTY = "persist.sys.safemode";
95     public static final String RO_SAFEMODE_PROPERTY = "ro.sys.safemode";
96 
97     // static instance of this thread
98     private static final ShutdownThread sInstance = new ShutdownThread();
99 
100     private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
101             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
102             .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
103             .build();
104 
105     // Metrics that will be reported to tron after reboot
106     private static final ArrayMap<String, Long> TRON_METRICS = new ArrayMap<>();
107 
108     // File to use for save metrics
109     private static final String METRICS_FILE_BASENAME = "/data/system/shutdown-metrics";
110 
111     // Metrics names to be persisted in shutdown-metrics file
112     private static String METRIC_SYSTEM_SERVER = "shutdown_system_server";
113     private static String METRIC_SEND_BROADCAST = "shutdown_send_shutdown_broadcast";
114     private static String METRIC_AM = "shutdown_activity_manager";
115     private static String METRIC_PM = "shutdown_package_manager";
116     private static String METRIC_RADIOS = "shutdown_radios";
117     private static String METRIC_RADIO = "shutdown_radio";
118     private static String METRIC_SHUTDOWN_TIME_START = "begin_shutdown";
119 
120     private final Object mActionDoneSync = new Object();
121     private boolean mActionDone;
122     private Context mContext;
123     private PowerManager mPowerManager;
124     private PowerManager.WakeLock mCpuWakeLock;
125     private PowerManager.WakeLock mScreenWakeLock;
126     private Handler mHandler;
127 
128     private static AlertDialog sConfirmDialog;
129     private ProgressDialog mProgressDialog;
130 
ShutdownThread()131     private ShutdownThread() {
132     }
133 
134     /**
135      * Request a clean shutdown, waiting for subsystems to clean up their
136      * state etc.  Must be called from a Looper thread in which its UI
137      * is shown.
138      *
139      * @param context Context used to display the shutdown progress dialog. This must be a context
140      *                suitable for displaying UI (aka Themable).
141      * @param reason code to pass to android_reboot() (e.g. "userrequested"), or null.
142      * @param confirm true if user confirmation is needed before shutting down.
143      */
shutdown(final Context context, String reason, boolean confirm)144     public static void shutdown(final Context context, String reason, boolean confirm) {
145         mReboot = false;
146         mRebootSafeMode = false;
147         mReason = reason;
148         shutdownInner(context, confirm);
149     }
150 
shutdownInner(final Context context, boolean confirm)151     private static void shutdownInner(final Context context, boolean confirm) {
152         // ShutdownThread is called from many places, so best to verify here that the context passed
153         // in is themed.
154         context.assertRuntimeOverlayThemable();
155 
156         // ensure that only one thread is trying to power down.
157         // any additional calls are just returned
158         synchronized (sIsStartedGuard) {
159             if (sIsStarted) {
160                 Log.d(TAG, "Request to shutdown already running, returning.");
161                 return;
162             }
163         }
164 
165         final int longPressBehavior = context.getResources().getInteger(
166                         com.android.internal.R.integer.config_longPressOnPowerBehavior);
167         final int resourceId = mRebootSafeMode
168                 ? com.android.internal.R.string.reboot_safemode_confirm
169                 : (longPressBehavior == 2
170                         ? com.android.internal.R.string.shutdown_confirm_question
171                         : com.android.internal.R.string.shutdown_confirm);
172 
173         Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
174 
175         if (confirm) {
176             final CloseDialogReceiver closer = new CloseDialogReceiver(context);
177             if (sConfirmDialog != null) {
178                 sConfirmDialog.dismiss();
179             }
180             sConfirmDialog = new AlertDialog.Builder(context)
181                     .setTitle(mRebootSafeMode
182                             ? com.android.internal.R.string.reboot_safemode_title
183                             : com.android.internal.R.string.power_off)
184                     .setMessage(resourceId)
185                     .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
186                         public void onClick(DialogInterface dialog, int which) {
187                             beginShutdownSequence(context);
188                         }
189                     })
190                     .setNegativeButton(com.android.internal.R.string.no, null)
191                     .create();
192             closer.dialog = sConfirmDialog;
193             sConfirmDialog.setOnDismissListener(closer);
194             sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
195             sConfirmDialog.show();
196         } else {
197             beginShutdownSequence(context);
198         }
199     }
200 
201     private static class CloseDialogReceiver extends BroadcastReceiver
202             implements DialogInterface.OnDismissListener {
203         private Context mContext;
204         public Dialog dialog;
205 
CloseDialogReceiver(Context context)206         CloseDialogReceiver(Context context) {
207             mContext = context;
208             IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
209             context.registerReceiver(this, filter);
210         }
211 
212         @Override
onReceive(Context context, Intent intent)213         public void onReceive(Context context, Intent intent) {
214             dialog.cancel();
215         }
216 
onDismiss(DialogInterface unused)217         public void onDismiss(DialogInterface unused) {
218             mContext.unregisterReceiver(this);
219         }
220     }
221 
222     /**
223      * Request a clean shutdown, waiting for subsystems to clean up their
224      * state etc.  Must be called from a Looper thread in which its UI
225      * is shown.
226      *
227      * @param context Context used to display the shutdown progress dialog. This must be a context
228      *                suitable for displaying UI (aka Themable).
229      * @param reason code to pass to the kernel (e.g. "recovery"), or null.
230      * @param confirm true if user confirmation is needed before shutting down.
231      */
reboot(final Context context, String reason, boolean confirm)232     public static void reboot(final Context context, String reason, boolean confirm) {
233         mReboot = true;
234         mRebootSafeMode = false;
235         mRebootHasProgressBar = false;
236         mReason = reason;
237         shutdownInner(context, confirm);
238     }
239 
240     /**
241      * Request a reboot into safe mode.  Must be called from a Looper thread in which its UI
242      * is shown.
243      *
244      * @param context Context used to display the shutdown progress dialog. This must be a context
245      *                suitable for displaying UI (aka Themable).
246      * @param confirm true if user confirmation is needed before shutting down.
247      */
rebootSafeMode(final Context context, boolean confirm)248     public static void rebootSafeMode(final Context context, boolean confirm) {
249         UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
250         if (um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
251             return;
252         }
253 
254         mReboot = true;
255         mRebootSafeMode = true;
256         mRebootHasProgressBar = false;
257         mReason = null;
258         shutdownInner(context, confirm);
259     }
260 
showShutdownDialog(Context context)261     private static ProgressDialog showShutdownDialog(Context context) {
262         // Throw up a system dialog to indicate the device is rebooting / shutting down.
263         ProgressDialog pd = new ProgressDialog(context);
264 
265         // Path 1: Reboot to recovery for update
266         //   Condition: mReason startswith REBOOT_RECOVERY_UPDATE
267         //
268         //  Path 1a: uncrypt needed
269         //   Condition: if /cache/recovery/uncrypt_file exists but
270         //              /cache/recovery/block.map doesn't.
271         //   UI: determinate progress bar (mRebootHasProgressBar == True)
272         //
273         // * Path 1a is expected to be removed once the GmsCore shipped on
274         //   device always calls uncrypt prior to reboot.
275         //
276         //  Path 1b: uncrypt already done
277         //   UI: spinning circle only (no progress bar)
278         //
279         // Path 2: Reboot to recovery for factory reset
280         //   Condition: mReason == REBOOT_RECOVERY
281         //   UI: spinning circle only (no progress bar)
282         //
283         // Path 3: Regular reboot / shutdown
284         //   Condition: Otherwise
285         //   UI: spinning circle only (no progress bar)
286 
287         // mReason could be "recovery-update" or "recovery-update,quiescent".
288         if (mReason != null && mReason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
289             // We need the progress bar if uncrypt will be invoked during the
290             // reboot, which might be time-consuming.
291             mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists()
292                     && !(RecoverySystem.BLOCK_MAP_FILE.exists());
293             pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
294             if (mRebootHasProgressBar) {
295                 pd.setMax(100);
296                 pd.setProgress(0);
297                 pd.setIndeterminate(false);
298                 pd.setProgressNumberFormat(null);
299                 pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
300                 pd.setMessage(context.getText(
301                             com.android.internal.R.string.reboot_to_update_prepare));
302             } else {
303                 if (showSysuiReboot()) {
304                     return null;
305                 }
306                 pd.setIndeterminate(true);
307                 pd.setMessage(context.getText(
308                             com.android.internal.R.string.reboot_to_update_reboot));
309             }
310         } else if (mReason != null && mReason.equals(PowerManager.REBOOT_RECOVERY)) {
311             if (RescueParty.isAttemptingFactoryReset()) {
312                 // We're not actually doing a factory reset yet; we're rebooting
313                 // to ask the user if they'd like to reset, so give them a less
314                 // scary dialog message.
315                 pd.setTitle(context.getText(com.android.internal.R.string.power_off));
316                 pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
317                 pd.setIndeterminate(true);
318             } else {
319                 // Factory reset path. Set the dialog message accordingly.
320                 pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
321                 pd.setMessage(context.getText(
322                             com.android.internal.R.string.reboot_to_reset_message));
323                 pd.setIndeterminate(true);
324             }
325         } else {
326             if (showSysuiReboot()) {
327                 return null;
328             }
329             pd.setTitle(context.getText(com.android.internal.R.string.power_off));
330             pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
331             pd.setIndeterminate(true);
332         }
333         pd.setCancelable(false);
334         pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
335 
336         pd.show();
337         return pd;
338     }
339 
showSysuiReboot()340     private static boolean showSysuiReboot() {
341         Log.d(TAG, "Attempting to use SysUI shutdown UI");
342         try {
343             StatusBarManagerInternal service = LocalServices.getService(
344                     StatusBarManagerInternal.class);
345             if (service.showShutdownUi(mReboot, mReason)) {
346                 // Sysui will handle shutdown UI.
347                 Log.d(TAG, "SysUI handling shutdown UI");
348                 return true;
349             }
350         } catch (Exception e) {
351             // If anything went wrong, ignore it and use fallback ui
352         }
353         Log.d(TAG, "SysUI is unavailable");
354         return false;
355     }
356 
beginShutdownSequence(Context context)357     private static void beginShutdownSequence(Context context) {
358         synchronized (sIsStartedGuard) {
359             if (sIsStarted) {
360                 Log.d(TAG, "Shutdown sequence already running, returning.");
361                 return;
362             }
363             sIsStarted = true;
364         }
365 
366         sInstance.mProgressDialog = showShutdownDialog(context);
367         sInstance.mContext = context;
368         sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
369 
370         // make sure we never fall asleep again
371         sInstance.mCpuWakeLock = null;
372         try {
373             sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
374                     PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
375             sInstance.mCpuWakeLock.setReferenceCounted(false);
376             sInstance.mCpuWakeLock.acquire();
377         } catch (SecurityException e) {
378             Log.w(TAG, "No permission to acquire wake lock", e);
379             sInstance.mCpuWakeLock = null;
380         }
381 
382         // also make sure the screen stays on for better user experience
383         sInstance.mScreenWakeLock = null;
384         if (sInstance.mPowerManager.isScreenOn()) {
385             try {
386                 sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
387                         PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
388                 sInstance.mScreenWakeLock.setReferenceCounted(false);
389                 sInstance.mScreenWakeLock.acquire();
390             } catch (SecurityException e) {
391                 Log.w(TAG, "No permission to acquire wake lock", e);
392                 sInstance.mScreenWakeLock = null;
393             }
394         }
395 
396         if (SecurityLog.isLoggingEnabled()) {
397             SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);
398         }
399 
400         // start the thread that initiates shutdown
401         sInstance.mHandler = new Handler() {
402         };
403         sInstance.start();
404     }
405 
actionDone()406     void actionDone() {
407         synchronized (mActionDoneSync) {
408             mActionDone = true;
409             mActionDoneSync.notifyAll();
410         }
411     }
412 
413     /**
414      * Makes sure we handle the shutdown gracefully.
415      * Shuts off power regardless of radio state if the allotted time has passed.
416      */
run()417     public void run() {
418         TimingsTraceLog shutdownTimingLog = newTimingsLog();
419         shutdownTimingLog.traceBegin("SystemServerShutdown");
420         metricShutdownStart();
421         metricStarted(METRIC_SYSTEM_SERVER);
422 
423         BroadcastReceiver br = new BroadcastReceiver() {
424             @Override public void onReceive(Context context, Intent intent) {
425                 // We don't allow apps to cancel this, so ignore the result.
426                 actionDone();
427             }
428         };
429 
430         /*
431          * Write a system property in case the system_server reboots before we
432          * get to the actual hardware restart. If that happens, we'll retry at
433          * the beginning of the SystemServer startup.
434          */
435         {
436             String reason = (mReboot ? "1" : "0") + (mReason != null ? mReason : "");
437             SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
438         }
439 
440         /*
441          * If we are rebooting into safe mode, write a system property
442          * indicating so.
443          */
444         if (mRebootSafeMode) {
445             SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
446         }
447 
448         shutdownTimingLog.traceBegin("DumpPreRebootInfo");
449         try {
450             Slog.i(TAG, "Logging pre-reboot information...");
451             PreRebootLogger.log(mContext);
452         } catch (Exception e) {
453             Slog.e(TAG, "Failed to log pre-reboot information", e);
454         }
455         shutdownTimingLog.traceEnd(); // DumpPreRebootInfo
456 
457         metricStarted(METRIC_SEND_BROADCAST);
458         shutdownTimingLog.traceBegin("SendShutdownBroadcast");
459         Log.i(TAG, "Sending shutdown broadcast...");
460 
461         // First send the high-level shut down broadcast.
462         mActionDone = false;
463         Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
464         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
465         mContext.sendOrderedBroadcastAsUser(intent,
466                 UserHandle.ALL, null, br, mHandler, 0, null, null);
467 
468         final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
469         synchronized (mActionDoneSync) {
470             while (!mActionDone) {
471                 long delay = endTime - SystemClock.elapsedRealtime();
472                 if (delay <= 0) {
473                     Log.w(TAG, "Shutdown broadcast timed out");
474                     break;
475                 } else if (mRebootHasProgressBar) {
476                     int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 *
477                             BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME);
478                     sInstance.setRebootProgress(status, null);
479                 }
480                 try {
481                     mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));
482                 } catch (InterruptedException e) {
483                 }
484             }
485         }
486         if (mRebootHasProgressBar) {
487             sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);
488         }
489         shutdownTimingLog.traceEnd(); // SendShutdownBroadcast
490         metricEnded(METRIC_SEND_BROADCAST);
491 
492         Log.i(TAG, "Shutting down activity manager...");
493         shutdownTimingLog.traceBegin("ShutdownActivityManager");
494         metricStarted(METRIC_AM);
495 
496         final IActivityManager am =
497                 IActivityManager.Stub.asInterface(ServiceManager.checkService("activity"));
498         if (am != null) {
499             try {
500                 am.shutdown(MAX_BROADCAST_TIME);
501             } catch (RemoteException e) {
502             }
503         }
504         if (mRebootHasProgressBar) {
505             sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
506         }
507         shutdownTimingLog.traceEnd();// ShutdownActivityManager
508         metricEnded(METRIC_AM);
509 
510         Log.i(TAG, "Shutting down package manager...");
511         shutdownTimingLog.traceBegin("ShutdownPackageManager");
512         metricStarted(METRIC_PM);
513 
514         final PackageManagerService pm = (PackageManagerService)
515             ServiceManager.getService("package");
516         if (pm != null) {
517             pm.shutdown();
518         }
519         if (mRebootHasProgressBar) {
520             sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
521         }
522         shutdownTimingLog.traceEnd(); // ShutdownPackageManager
523         metricEnded(METRIC_PM);
524 
525         // Shutdown radios.
526         shutdownTimingLog.traceBegin("ShutdownRadios");
527         metricStarted(METRIC_RADIOS);
528         shutdownRadios(MAX_RADIO_WAIT_TIME);
529         if (mRebootHasProgressBar) {
530             sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
531         }
532         shutdownTimingLog.traceEnd(); // ShutdownRadios
533         metricEnded(METRIC_RADIOS);
534 
535         if (mRebootHasProgressBar) {
536             sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);
537 
538             // If it's to reboot to install an update and uncrypt hasn't been
539             // done yet, trigger it now.
540             uncrypt();
541         }
542 
543         shutdownTimingLog.traceEnd(); // SystemServerShutdown
544         metricEnded(METRIC_SYSTEM_SERVER);
545         saveMetrics(mReboot, mReason);
546         // Remaining work will be done by init, including vold shutdown
547         rebootOrShutdown(mContext, mReboot, mReason);
548     }
549 
newTimingsLog()550     private static TimingsTraceLog newTimingsLog() {
551         return new TimingsTraceLog("ShutdownTiming", Trace.TRACE_TAG_SYSTEM_SERVER);
552     }
553 
metricStarted(String metricKey)554     private static void metricStarted(String metricKey) {
555         synchronized (TRON_METRICS) {
556             TRON_METRICS.put(metricKey, -1 * SystemClock.elapsedRealtime());
557         }
558     }
559 
metricEnded(String metricKey)560     private static void metricEnded(String metricKey) {
561         synchronized (TRON_METRICS) {
562             TRON_METRICS
563                     .put(metricKey, SystemClock.elapsedRealtime() + TRON_METRICS.get(metricKey));
564         }
565     }
566 
metricShutdownStart()567     private static void metricShutdownStart() {
568         synchronized (TRON_METRICS) {
569             TRON_METRICS.put(METRIC_SHUTDOWN_TIME_START, System.currentTimeMillis());
570         }
571     }
572 
setRebootProgress(final int progress, final CharSequence message)573     private void setRebootProgress(final int progress, final CharSequence message) {
574         mHandler.post(new Runnable() {
575             @Override
576             public void run() {
577                 if (mProgressDialog != null) {
578                     mProgressDialog.setProgress(progress);
579                     if (message != null) {
580                         mProgressDialog.setMessage(message);
581                     }
582                 }
583             }
584         });
585     }
586 
shutdownRadios(final int timeout)587     private void shutdownRadios(final int timeout) {
588         // If a radio is wedged, disabling it may hang so we do this work in another thread,
589         // just in case.
590         final long endTime = SystemClock.elapsedRealtime() + timeout;
591         final boolean[] done = new boolean[1];
592         Thread t = new Thread() {
593             public void run() {
594                 TimingsTraceLog shutdownTimingsTraceLog = newTimingsLog();
595                 boolean radioOff;
596 
597                 TelephonyManager telephonyManager = mContext.getSystemService(
598                         TelephonyManager.class);
599 
600                 radioOff = telephonyManager == null
601                         || !telephonyManager.isAnyRadioPoweredOn();
602                 if (!radioOff) {
603                     Log.w(TAG, "Turning off cellular radios...");
604                     metricStarted(METRIC_RADIO);
605                     telephonyManager.shutdownAllRadios();
606                 }
607 
608                 Log.i(TAG, "Waiting for Radio...");
609 
610                 long delay = endTime - SystemClock.elapsedRealtime();
611                 while (delay > 0) {
612                     if (mRebootHasProgressBar) {
613                         int status = (int)((timeout - delay) * 1.0 *
614                                 (RADIO_STOP_PERCENT - PACKAGE_MANAGER_STOP_PERCENT) / timeout);
615                         status += PACKAGE_MANAGER_STOP_PERCENT;
616                         sInstance.setRebootProgress(status, null);
617                     }
618 
619                     if (!radioOff) {
620                         radioOff = !telephonyManager.isAnyRadioPoweredOn();
621                         if (radioOff) {
622                             Log.i(TAG, "Radio turned off.");
623                             metricEnded(METRIC_RADIO);
624                             shutdownTimingsTraceLog
625                                     .logDuration("ShutdownRadio", TRON_METRICS.get(METRIC_RADIO));
626                         }
627                     }
628 
629                     if (radioOff) {
630                         Log.i(TAG, "Radio shutdown complete.");
631                         done[0] = true;
632                         break;
633                     }
634                     SystemClock.sleep(RADIOS_STATE_POLL_SLEEP_MS);
635                     delay = endTime - SystemClock.elapsedRealtime();
636                 }
637             }
638         };
639 
640         t.start();
641         try {
642             t.join(timeout);
643         } catch (InterruptedException ex) {
644         }
645         if (!done[0]) {
646             Log.w(TAG, "Timed out waiting for Radio shutdown.");
647         }
648     }
649 
650     /**
651      * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
652      * or {@link #shutdown(Context, String, boolean)} instead.
653      *
654      * @param context Context used to vibrate or null without vibration
655      * @param reboot true to reboot or false to shutdown
656      * @param reason reason for reboot/shutdown
657      */
rebootOrShutdown(final Context context, boolean reboot, String reason)658     public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
659         if (reboot) {
660             Log.i(TAG, "Rebooting, reason: " + reason);
661             PowerManagerService.lowLevelReboot(reason);
662             Log.e(TAG, "Reboot failed, will attempt shutdown instead");
663             reason = null;
664         } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
665             // vibrate before shutting down
666             Vibrator vibrator = new SystemVibrator(context);
667             try {
668                 vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
669             } catch (Exception e) {
670                 // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
671                 Log.w(TAG, "Failed to vibrate during shutdown.", e);
672             }
673 
674             // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
675             try {
676                 Thread.sleep(SHUTDOWN_VIBRATE_MS);
677             } catch (InterruptedException unused) {
678             }
679         }
680         // Shutdown power
681         Log.i(TAG, "Performing low-level shutdown...");
682         PowerManagerService.lowLevelShutdown(reason);
683     }
684 
saveMetrics(boolean reboot, String reason)685     private static void saveMetrics(boolean reboot, String reason) {
686         StringBuilder metricValue = new StringBuilder();
687         metricValue.append("reboot:");
688         metricValue.append(reboot ? "y" : "n");
689         metricValue.append(",").append("reason:").append(reason);
690         final int metricsSize = TRON_METRICS.size();
691         for (int i = 0; i < metricsSize; i++) {
692             final String name = TRON_METRICS.keyAt(i);
693             final long value = TRON_METRICS.valueAt(i);
694             if (value < 0) {
695                 Log.e(TAG, "metricEnded wasn't called for " + name);
696                 continue;
697             }
698             metricValue.append(',').append(name).append(':').append(value);
699         }
700         File tmp = new File(METRICS_FILE_BASENAME + ".tmp");
701         boolean saved = false;
702         try (FileOutputStream fos = new FileOutputStream(tmp)) {
703             fos.write(metricValue.toString().getBytes(StandardCharsets.UTF_8));
704             saved = true;
705         } catch (IOException e) {
706             Log.e(TAG,"Cannot save shutdown metrics", e);
707         }
708         if (saved) {
709             tmp.renameTo(new File(METRICS_FILE_BASENAME + ".txt"));
710         }
711     }
712 
uncrypt()713     private void uncrypt() {
714         Log.i(TAG, "Calling uncrypt and monitoring the progress...");
715 
716         final RecoverySystem.ProgressListener progressListener =
717                 new RecoverySystem.ProgressListener() {
718             @Override
719             public void onProgress(int status) {
720                 if (status >= 0 && status < 100) {
721                     // Scale down to [MOUNT_SERVICE_STOP_PERCENT, 100).
722                     status = (int)(status * (100.0 - MOUNT_SERVICE_STOP_PERCENT) / 100);
723                     status += MOUNT_SERVICE_STOP_PERCENT;
724                     CharSequence msg = mContext.getText(
725                             com.android.internal.R.string.reboot_to_update_package);
726                     sInstance.setRebootProgress(status, msg);
727                 } else if (status == 100) {
728                     CharSequence msg = mContext.getText(
729                             com.android.internal.R.string.reboot_to_update_reboot);
730                     sInstance.setRebootProgress(status, msg);
731                 } else {
732                     // Ignored
733                 }
734             }
735         };
736 
737         final boolean[] done = new boolean[1];
738         done[0] = false;
739         Thread t = new Thread() {
740             @Override
741             public void run() {
742                 RecoverySystem rs = (RecoverySystem) mContext.getSystemService(
743                         Context.RECOVERY_SERVICE);
744                 String filename = null;
745                 try {
746                     filename = FileUtils.readTextFile(RecoverySystem.UNCRYPT_PACKAGE_FILE, 0, null);
747                     rs.processPackage(mContext, new File(filename), progressListener);
748                 } catch (IOException e) {
749                     Log.e(TAG, "Error uncrypting file", e);
750                 }
751                 done[0] = true;
752             }
753         };
754         t.start();
755 
756         try {
757             t.join(MAX_UNCRYPT_WAIT_TIME);
758         } catch (InterruptedException unused) {
759         }
760         if (!done[0]) {
761             Log.w(TAG, "Timed out waiting for uncrypt.");
762             final int uncryptTimeoutError = 100;
763             String timeoutMessage = String.format("uncrypt_time: %d\n" + "uncrypt_error: %d\n",
764                     MAX_UNCRYPT_WAIT_TIME / 1000, uncryptTimeoutError);
765             try {
766                 FileUtils.stringToFile(RecoverySystem.UNCRYPT_STATUS_FILE, timeoutMessage);
767             } catch (IOException e) {
768                 Log.e(TAG, "Failed to write timeout message to uncrypt status", e);
769             }
770         }
771     }
772 }
773