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