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