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 (showSysuiReboot()) {
312                 return null;
313             } else if (RescueParty.isAttemptingFactoryReset()) {
314                 // We're not actually doing a factory reset yet; we're rebooting
315                 // to ask the user if they'd like to reset, so give them a less
316                 // scary dialog message.
317                 pd.setTitle(context.getText(com.android.internal.R.string.power_off));
318                 pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
319                 pd.setIndeterminate(true);
320             } else {
321                 // Factory reset path. Set the dialog message accordingly.
322                 pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
323                 pd.setMessage(context.getText(
324                             com.android.internal.R.string.reboot_to_reset_message));
325                 pd.setIndeterminate(true);
326             }
327         } else {
328             if (showSysuiReboot()) {
329                 return null;
330             }
331             pd.setTitle(context.getText(com.android.internal.R.string.power_off));
332             pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
333             pd.setIndeterminate(true);
334         }
335         pd.setCancelable(false);
336         pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
337 
338         pd.show();
339         return pd;
340     }
341 
showSysuiReboot()342     private static boolean showSysuiReboot() {
343         Log.d(TAG, "Attempting to use SysUI shutdown UI");
344         try {
345             StatusBarManagerInternal service = LocalServices.getService(
346                     StatusBarManagerInternal.class);
347             if (service.showShutdownUi(mReboot, mReason)) {
348                 // Sysui will handle shutdown UI.
349                 Log.d(TAG, "SysUI handling shutdown UI");
350                 return true;
351             }
352         } catch (Exception e) {
353             // If anything went wrong, ignore it and use fallback ui
354         }
355         Log.d(TAG, "SysUI is unavailable");
356         return false;
357     }
358 
beginShutdownSequence(Context context)359     private static void beginShutdownSequence(Context context) {
360         synchronized (sIsStartedGuard) {
361             if (sIsStarted) {
362                 Log.d(TAG, "Shutdown sequence already running, returning.");
363                 return;
364             }
365             sIsStarted = true;
366         }
367 
368         sInstance.mProgressDialog = showShutdownDialog(context);
369         sInstance.mContext = context;
370         sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
371 
372         // make sure we never fall asleep again
373         sInstance.mCpuWakeLock = null;
374         try {
375             sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
376                     PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
377             sInstance.mCpuWakeLock.setReferenceCounted(false);
378             sInstance.mCpuWakeLock.acquire();
379         } catch (SecurityException e) {
380             Log.w(TAG, "No permission to acquire wake lock", e);
381             sInstance.mCpuWakeLock = null;
382         }
383 
384         // also make sure the screen stays on for better user experience
385         sInstance.mScreenWakeLock = null;
386         if (sInstance.mPowerManager.isScreenOn()) {
387             try {
388                 sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
389                         PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
390                 sInstance.mScreenWakeLock.setReferenceCounted(false);
391                 sInstance.mScreenWakeLock.acquire();
392             } catch (SecurityException e) {
393                 Log.w(TAG, "No permission to acquire wake lock", e);
394                 sInstance.mScreenWakeLock = null;
395             }
396         }
397 
398         if (SecurityLog.isLoggingEnabled()) {
399             SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);
400         }
401 
402         // start the thread that initiates shutdown
403         sInstance.mHandler = new Handler() {
404         };
405         sInstance.start();
406     }
407 
actionDone()408     void actionDone() {
409         synchronized (mActionDoneSync) {
410             mActionDone = true;
411             mActionDoneSync.notifyAll();
412         }
413     }
414 
415     /**
416      * Makes sure we handle the shutdown gracefully.
417      * Shuts off power regardless of radio state if the allotted time has passed.
418      */
run()419     public void run() {
420         TimingsTraceLog shutdownTimingLog = newTimingsLog();
421         shutdownTimingLog.traceBegin("SystemServerShutdown");
422         metricShutdownStart();
423         metricStarted(METRIC_SYSTEM_SERVER);
424 
425         BroadcastReceiver br = new BroadcastReceiver() {
426             @Override public void onReceive(Context context, Intent intent) {
427                 // We don't allow apps to cancel this, so ignore the result.
428                 actionDone();
429             }
430         };
431 
432         /*
433          * Write a system property in case the system_server reboots before we
434          * get to the actual hardware restart. If that happens, we'll retry at
435          * the beginning of the SystemServer startup.
436          */
437         {
438             String reason = (mReboot ? "1" : "0") + (mReason != null ? mReason : "");
439             SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
440         }
441 
442         /*
443          * If we are rebooting into safe mode, write a system property
444          * indicating so.
445          */
446         if (mRebootSafeMode) {
447             SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
448         }
449 
450         shutdownTimingLog.traceBegin("DumpPreRebootInfo");
451         try {
452             Slog.i(TAG, "Logging pre-reboot information...");
453             PreRebootLogger.log(mContext);
454         } catch (Exception e) {
455             Slog.e(TAG, "Failed to log pre-reboot information", e);
456         }
457         shutdownTimingLog.traceEnd(); // DumpPreRebootInfo
458 
459         metricStarted(METRIC_SEND_BROADCAST);
460         shutdownTimingLog.traceBegin("SendShutdownBroadcast");
461         Log.i(TAG, "Sending shutdown broadcast...");
462 
463         // First send the high-level shut down broadcast.
464         mActionDone = false;
465         Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
466         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
467         mContext.sendOrderedBroadcastAsUser(intent,
468                 UserHandle.ALL, null, br, mHandler, 0, null, null);
469 
470         final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
471         synchronized (mActionDoneSync) {
472             while (!mActionDone) {
473                 long delay = endTime - SystemClock.elapsedRealtime();
474                 if (delay <= 0) {
475                     Log.w(TAG, "Shutdown broadcast timed out");
476                     break;
477                 } else if (mRebootHasProgressBar) {
478                     int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 *
479                             BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME);
480                     sInstance.setRebootProgress(status, null);
481                 }
482                 try {
483                     mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));
484                 } catch (InterruptedException e) {
485                 }
486             }
487         }
488         if (mRebootHasProgressBar) {
489             sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);
490         }
491         shutdownTimingLog.traceEnd(); // SendShutdownBroadcast
492         metricEnded(METRIC_SEND_BROADCAST);
493 
494         Log.i(TAG, "Shutting down activity manager...");
495         shutdownTimingLog.traceBegin("ShutdownActivityManager");
496         metricStarted(METRIC_AM);
497 
498         final IActivityManager am =
499                 IActivityManager.Stub.asInterface(ServiceManager.checkService("activity"));
500         if (am != null) {
501             try {
502                 am.shutdown(MAX_BROADCAST_TIME);
503             } catch (RemoteException e) {
504             }
505         }
506         if (mRebootHasProgressBar) {
507             sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
508         }
509         shutdownTimingLog.traceEnd();// ShutdownActivityManager
510         metricEnded(METRIC_AM);
511 
512         Log.i(TAG, "Shutting down package manager...");
513         shutdownTimingLog.traceBegin("ShutdownPackageManager");
514         metricStarted(METRIC_PM);
515 
516         final PackageManagerService pm = (PackageManagerService)
517             ServiceManager.getService("package");
518         if (pm != null) {
519             pm.shutdown();
520         }
521         if (mRebootHasProgressBar) {
522             sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
523         }
524         shutdownTimingLog.traceEnd(); // ShutdownPackageManager
525         metricEnded(METRIC_PM);
526 
527         // Shutdown radios.
528         shutdownTimingLog.traceBegin("ShutdownRadios");
529         metricStarted(METRIC_RADIOS);
530         shutdownRadios(MAX_RADIO_WAIT_TIME);
531         if (mRebootHasProgressBar) {
532             sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
533         }
534         shutdownTimingLog.traceEnd(); // ShutdownRadios
535         metricEnded(METRIC_RADIOS);
536 
537         if (mRebootHasProgressBar) {
538             sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);
539 
540             // If it's to reboot to install an update and uncrypt hasn't been
541             // done yet, trigger it now.
542             uncrypt();
543         }
544 
545         shutdownTimingLog.traceEnd(); // SystemServerShutdown
546         metricEnded(METRIC_SYSTEM_SERVER);
547         saveMetrics(mReboot, mReason);
548         // Remaining work will be done by init, including vold shutdown
549         rebootOrShutdown(mContext, mReboot, mReason);
550     }
551 
newTimingsLog()552     private static TimingsTraceLog newTimingsLog() {
553         return new TimingsTraceLog("ShutdownTiming", Trace.TRACE_TAG_SYSTEM_SERVER);
554     }
555 
metricStarted(String metricKey)556     private static void metricStarted(String metricKey) {
557         synchronized (TRON_METRICS) {
558             TRON_METRICS.put(metricKey, -1 * SystemClock.elapsedRealtime());
559         }
560     }
561 
metricEnded(String metricKey)562     private static void metricEnded(String metricKey) {
563         synchronized (TRON_METRICS) {
564             TRON_METRICS
565                     .put(metricKey, SystemClock.elapsedRealtime() + TRON_METRICS.get(metricKey));
566         }
567     }
568 
metricShutdownStart()569     private static void metricShutdownStart() {
570         synchronized (TRON_METRICS) {
571             TRON_METRICS.put(METRIC_SHUTDOWN_TIME_START, System.currentTimeMillis());
572         }
573     }
574 
setRebootProgress(final int progress, final CharSequence message)575     private void setRebootProgress(final int progress, final CharSequence message) {
576         mHandler.post(new Runnable() {
577             @Override
578             public void run() {
579                 if (mProgressDialog != null) {
580                     mProgressDialog.setProgress(progress);
581                     if (message != null) {
582                         mProgressDialog.setMessage(message);
583                     }
584                 }
585             }
586         });
587     }
588 
shutdownRadios(final int timeout)589     private void shutdownRadios(final int timeout) {
590         // If a radio is wedged, disabling it may hang so we do this work in another thread,
591         // just in case.
592         final long endTime = SystemClock.elapsedRealtime() + timeout;
593         final boolean[] done = new boolean[1];
594         Thread t = new Thread() {
595             public void run() {
596                 TimingsTraceLog shutdownTimingsTraceLog = newTimingsLog();
597                 boolean radioOff;
598 
599                 TelephonyManager telephonyManager = mContext.getSystemService(
600                         TelephonyManager.class);
601 
602                 radioOff = telephonyManager == null
603                         || !telephonyManager.isAnyRadioPoweredOn();
604                 if (!radioOff) {
605                     Log.w(TAG, "Turning off cellular radios...");
606                     metricStarted(METRIC_RADIO);
607                     telephonyManager.shutdownAllRadios();
608                 }
609 
610                 Log.i(TAG, "Waiting for Radio...");
611 
612                 long delay = endTime - SystemClock.elapsedRealtime();
613                 while (delay > 0) {
614                     if (mRebootHasProgressBar) {
615                         int status = (int)((timeout - delay) * 1.0 *
616                                 (RADIO_STOP_PERCENT - PACKAGE_MANAGER_STOP_PERCENT) / timeout);
617                         status += PACKAGE_MANAGER_STOP_PERCENT;
618                         sInstance.setRebootProgress(status, null);
619                     }
620 
621                     if (!radioOff) {
622                         radioOff = !telephonyManager.isAnyRadioPoweredOn();
623                         if (radioOff) {
624                             Log.i(TAG, "Radio turned off.");
625                             metricEnded(METRIC_RADIO);
626                             shutdownTimingsTraceLog
627                                     .logDuration("ShutdownRadio", TRON_METRICS.get(METRIC_RADIO));
628                         }
629                     }
630 
631                     if (radioOff) {
632                         Log.i(TAG, "Radio shutdown complete.");
633                         done[0] = true;
634                         break;
635                     }
636                     SystemClock.sleep(RADIOS_STATE_POLL_SLEEP_MS);
637                     delay = endTime - SystemClock.elapsedRealtime();
638                 }
639             }
640         };
641 
642         t.start();
643         try {
644             t.join(timeout);
645         } catch (InterruptedException ex) {
646         }
647         if (!done[0]) {
648             Log.w(TAG, "Timed out waiting for Radio shutdown.");
649         }
650     }
651 
652     /**
653      * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
654      * or {@link #shutdown(Context, String, boolean)} instead.
655      *
656      * @param context Context used to vibrate or null without vibration
657      * @param reboot true to reboot or false to shutdown
658      * @param reason reason for reboot/shutdown
659      */
rebootOrShutdown(final Context context, boolean reboot, String reason)660     public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
661         if (reboot) {
662             Log.i(TAG, "Rebooting, reason: " + reason);
663             PowerManagerService.lowLevelReboot(reason);
664             Log.e(TAG, "Reboot failed, will attempt shutdown instead");
665             reason = null;
666         } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
667             // vibrate before shutting down
668             Vibrator vibrator = new SystemVibrator(context);
669             try {
670                 vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
671             } catch (Exception e) {
672                 // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
673                 Log.w(TAG, "Failed to vibrate during shutdown.", e);
674             }
675 
676             // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
677             try {
678                 Thread.sleep(SHUTDOWN_VIBRATE_MS);
679             } catch (InterruptedException unused) {
680             }
681         }
682         // Shutdown power
683         Log.i(TAG, "Performing low-level shutdown...");
684         PowerManagerService.lowLevelShutdown(reason);
685     }
686 
saveMetrics(boolean reboot, String reason)687     private static void saveMetrics(boolean reboot, String reason) {
688         StringBuilder metricValue = new StringBuilder();
689         metricValue.append("reboot:");
690         metricValue.append(reboot ? "y" : "n");
691         metricValue.append(",").append("reason:").append(reason);
692         final int metricsSize = TRON_METRICS.size();
693         for (int i = 0; i < metricsSize; i++) {
694             final String name = TRON_METRICS.keyAt(i);
695             final long value = TRON_METRICS.valueAt(i);
696             if (value < 0) {
697                 Log.e(TAG, "metricEnded wasn't called for " + name);
698                 continue;
699             }
700             metricValue.append(',').append(name).append(':').append(value);
701         }
702         File tmp = new File(METRICS_FILE_BASENAME + ".tmp");
703         boolean saved = false;
704         try (FileOutputStream fos = new FileOutputStream(tmp)) {
705             fos.write(metricValue.toString().getBytes(StandardCharsets.UTF_8));
706             saved = true;
707         } catch (IOException e) {
708             Log.e(TAG,"Cannot save shutdown metrics", e);
709         }
710         if (saved) {
711             tmp.renameTo(new File(METRICS_FILE_BASENAME + ".txt"));
712         }
713     }
714 
uncrypt()715     private void uncrypt() {
716         Log.i(TAG, "Calling uncrypt and monitoring the progress...");
717 
718         final RecoverySystem.ProgressListener progressListener =
719                 new RecoverySystem.ProgressListener() {
720             @Override
721             public void onProgress(int status) {
722                 if (status >= 0 && status < 100) {
723                     // Scale down to [MOUNT_SERVICE_STOP_PERCENT, 100).
724                     status = (int)(status * (100.0 - MOUNT_SERVICE_STOP_PERCENT) / 100);
725                     status += MOUNT_SERVICE_STOP_PERCENT;
726                     CharSequence msg = mContext.getText(
727                             com.android.internal.R.string.reboot_to_update_package);
728                     sInstance.setRebootProgress(status, msg);
729                 } else if (status == 100) {
730                     CharSequence msg = mContext.getText(
731                             com.android.internal.R.string.reboot_to_update_reboot);
732                     sInstance.setRebootProgress(status, msg);
733                 } else {
734                     // Ignored
735                 }
736             }
737         };
738 
739         final boolean[] done = new boolean[1];
740         done[0] = false;
741         Thread t = new Thread() {
742             @Override
743             public void run() {
744                 RecoverySystem rs = (RecoverySystem) mContext.getSystemService(
745                         Context.RECOVERY_SERVICE);
746                 String filename = null;
747                 try {
748                     filename = FileUtils.readTextFile(RecoverySystem.UNCRYPT_PACKAGE_FILE, 0, null);
749                     rs.processPackage(mContext, new File(filename), progressListener);
750                 } catch (IOException e) {
751                     Log.e(TAG, "Error uncrypting file", e);
752                 }
753                 done[0] = true;
754             }
755         };
756         t.start();
757 
758         try {
759             t.join(MAX_UNCRYPT_WAIT_TIME);
760         } catch (InterruptedException unused) {
761         }
762         if (!done[0]) {
763             Log.w(TAG, "Timed out waiting for uncrypt.");
764             final int uncryptTimeoutError = 100;
765             String timeoutMessage = String.format("uncrypt_time: %d\n" + "uncrypt_error: %d\n",
766                     MAX_UNCRYPT_WAIT_TIME / 1000, uncryptTimeoutError);
767             try {
768                 FileUtils.stringToFile(RecoverySystem.UNCRYPT_STATUS_FILE, timeoutMessage);
769             } catch (IOException e) {
770                 Log.e(TAG, "Failed to write timeout message to uncrypt status", e);
771             }
772         }
773     }
774 }
775