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_CHECK_POINTS_DUMP_WAIT_TIME = 10 * 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 saving shutdown metrics
109     private static final String METRICS_FILE_BASENAME = "/data/system/shutdown-metrics";
110     // File to use for saving shutdown check points
111     private static final String CHECK_POINTS_FILE_BASENAME =
112             "/data/system/shutdown-checkpoints/checkpoints";
113 
114     // Metrics names to be persisted in shutdown-metrics file
115     private static String METRIC_SYSTEM_SERVER = "shutdown_system_server";
116     private static String METRIC_SEND_BROADCAST = "shutdown_send_shutdown_broadcast";
117     private static String METRIC_AM = "shutdown_activity_manager";
118     private static String METRIC_PM = "shutdown_package_manager";
119     private static String METRIC_RADIOS = "shutdown_radios";
120     private static String METRIC_RADIO = "shutdown_radio";
121     private static String METRIC_SHUTDOWN_TIME_START = "begin_shutdown";
122 
123     private final Object mActionDoneSync = new Object();
124     private boolean mActionDone;
125     private Context mContext;
126     private PowerManager mPowerManager;
127     private PowerManager.WakeLock mCpuWakeLock;
128     private PowerManager.WakeLock mScreenWakeLock;
129     private Handler mHandler;
130 
131     private static AlertDialog sConfirmDialog;
132     private ProgressDialog mProgressDialog;
133 
ShutdownThread()134     private ShutdownThread() {
135     }
136 
137     /**
138      * Request a clean shutdown, waiting for subsystems to clean up their
139      * state etc.  Must be called from a Looper thread in which its UI
140      * is shown.
141      *
142      * @param context Context used to display the shutdown progress dialog. This must be a context
143      *                suitable for displaying UI (aka Themable).
144      * @param reason code to pass to android_reboot() (e.g. "userrequested"), or null.
145      * @param confirm true if user confirmation is needed before shutting down.
146      */
shutdown(final Context context, String reason, boolean confirm)147     public static void shutdown(final Context context, String reason, boolean confirm) {
148         mReboot = false;
149         mRebootSafeMode = false;
150         mReason = reason;
151         shutdownInner(context, confirm);
152     }
153 
shutdownInner(final Context context, boolean confirm)154     private static void shutdownInner(final Context context, boolean confirm) {
155         // ShutdownThread is called from many places, so best to verify here that the context passed
156         // in is themed.
157         context.assertRuntimeOverlayThemable();
158 
159         // ensure that only one thread is trying to power down.
160         // any additional calls are just returned
161         synchronized (sIsStartedGuard) {
162             if (sIsStarted) {
163                 Log.d(TAG, "Request to shutdown already running, returning.");
164                 return;
165             }
166         }
167 
168         // Add checkpoint for this shutdown attempt. The user might still cancel the dialog, but
169         // this point preserves the system trace of the trigger point of the ShutdownThread.
170         ShutdownCheckPoints.recordCheckPoint(/* reason= */ null);
171 
172         final int longPressBehavior = context.getResources().getInteger(
173                         com.android.internal.R.integer.config_longPressOnPowerBehavior);
174         final int resourceId = mRebootSafeMode
175                 ? com.android.internal.R.string.reboot_safemode_confirm
176                 : (longPressBehavior == 2
177                         ? com.android.internal.R.string.shutdown_confirm_question
178                         : com.android.internal.R.string.shutdown_confirm);
179 
180         Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
181 
182         if (confirm) {
183             final CloseDialogReceiver closer = new CloseDialogReceiver(context);
184             if (sConfirmDialog != null) {
185                 sConfirmDialog.dismiss();
186             }
187             sConfirmDialog = new AlertDialog.Builder(context)
188                     .setTitle(mRebootSafeMode
189                             ? com.android.internal.R.string.reboot_safemode_title
190                             : com.android.internal.R.string.power_off)
191                     .setMessage(resourceId)
192                     .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
193                         public void onClick(DialogInterface dialog, int which) {
194                             beginShutdownSequence(context);
195                         }
196                     })
197                     .setNegativeButton(com.android.internal.R.string.no, null)
198                     .create();
199             closer.dialog = sConfirmDialog;
200             sConfirmDialog.setOnDismissListener(closer);
201             sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
202             sConfirmDialog.show();
203         } else {
204             beginShutdownSequence(context);
205         }
206     }
207 
208     private static class CloseDialogReceiver extends BroadcastReceiver
209             implements DialogInterface.OnDismissListener {
210         private Context mContext;
211         public Dialog dialog;
212 
CloseDialogReceiver(Context context)213         CloseDialogReceiver(Context context) {
214             mContext = context;
215             IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
216             context.registerReceiver(this, filter);
217         }
218 
219         @Override
onReceive(Context context, Intent intent)220         public void onReceive(Context context, Intent intent) {
221             dialog.cancel();
222         }
223 
onDismiss(DialogInterface unused)224         public void onDismiss(DialogInterface unused) {
225             mContext.unregisterReceiver(this);
226         }
227     }
228 
229     /**
230      * Request a clean shutdown, waiting for subsystems to clean up their
231      * state etc.  Must be called from a Looper thread in which its UI
232      * is shown.
233      *
234      * @param context Context used to display the shutdown progress dialog. This must be a context
235      *                suitable for displaying UI (aka Themable).
236      * @param reason code to pass to the kernel (e.g. "recovery"), or null.
237      * @param confirm true if user confirmation is needed before shutting down.
238      */
reboot(final Context context, String reason, boolean confirm)239     public static void reboot(final Context context, String reason, boolean confirm) {
240         mReboot = true;
241         mRebootSafeMode = false;
242         mRebootHasProgressBar = false;
243         mReason = reason;
244         shutdownInner(context, confirm);
245     }
246 
247     /**
248      * Request a reboot into safe mode.  Must be called from a Looper thread in which its UI
249      * is shown.
250      *
251      * @param context Context used to display the shutdown progress dialog. This must be a context
252      *                suitable for displaying UI (aka Themable).
253      * @param confirm true if user confirmation is needed before shutting down.
254      */
rebootSafeMode(final Context context, boolean confirm)255     public static void rebootSafeMode(final Context context, boolean confirm) {
256         UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
257         if (um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
258             return;
259         }
260 
261         mReboot = true;
262         mRebootSafeMode = true;
263         mRebootHasProgressBar = false;
264         mReason = null;
265         shutdownInner(context, confirm);
266     }
267 
showShutdownDialog(Context context)268     private static ProgressDialog showShutdownDialog(Context context) {
269         // Throw up a system dialog to indicate the device is rebooting / shutting down.
270         ProgressDialog pd = new ProgressDialog(context);
271 
272         // Path 1: Reboot to recovery for update
273         //   Condition: mReason startswith REBOOT_RECOVERY_UPDATE
274         //
275         //  Path 1a: uncrypt needed
276         //   Condition: if /cache/recovery/uncrypt_file exists but
277         //              /cache/recovery/block.map doesn't.
278         //   UI: determinate progress bar (mRebootHasProgressBar == True)
279         //
280         // * Path 1a is expected to be removed once the GmsCore shipped on
281         //   device always calls uncrypt prior to reboot.
282         //
283         //  Path 1b: uncrypt already done
284         //   UI: spinning circle only (no progress bar)
285         //
286         // Path 2: Reboot to recovery for factory reset
287         //   Condition: mReason == REBOOT_RECOVERY
288         //   UI: spinning circle only (no progress bar)
289         //
290         // Path 3: Regular reboot / shutdown
291         //   Condition: Otherwise
292         //   UI: spinning circle only (no progress bar)
293 
294         // mReason could be "recovery-update" or "recovery-update,quiescent".
295         if (mReason != null && mReason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
296             // We need the progress bar if uncrypt will be invoked during the
297             // reboot, which might be time-consuming.
298             mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists()
299                     && !(RecoverySystem.BLOCK_MAP_FILE.exists());
300             pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
301             if (mRebootHasProgressBar) {
302                 pd.setMax(100);
303                 pd.setProgress(0);
304                 pd.setIndeterminate(false);
305                 pd.setProgressNumberFormat(null);
306                 pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
307                 pd.setMessage(context.getText(
308                             com.android.internal.R.string.reboot_to_update_prepare));
309             } else {
310                 if (showSysuiReboot()) {
311                     return null;
312                 }
313                 pd.setIndeterminate(true);
314                 pd.setMessage(context.getText(
315                             com.android.internal.R.string.reboot_to_update_reboot));
316             }
317         } else if (mReason != null && mReason.equals(PowerManager.REBOOT_RECOVERY)) {
318             if (showSysuiReboot()) {
319                 return null;
320             } else if (RescueParty.isAttemptingFactoryReset()) {
321                 // We're not actually doing a factory reset yet; we're rebooting
322                 // to ask the user if they'd like to reset, so give them a less
323                 // scary dialog message.
324                 pd.setTitle(context.getText(com.android.internal.R.string.power_off));
325                 pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
326                 pd.setIndeterminate(true);
327             } else {
328                 // Factory reset path. Set the dialog message accordingly.
329                 pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
330                 pd.setMessage(context.getText(
331                             com.android.internal.R.string.reboot_to_reset_message));
332                 pd.setIndeterminate(true);
333             }
334         } else {
335             if (showSysuiReboot()) {
336                 return null;
337             }
338             pd.setTitle(context.getText(com.android.internal.R.string.power_off));
339             pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
340             pd.setIndeterminate(true);
341         }
342         pd.setCancelable(false);
343         pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
344 
345         pd.show();
346         return pd;
347     }
348 
showSysuiReboot()349     private static boolean showSysuiReboot() {
350         Log.d(TAG, "Attempting to use SysUI shutdown UI");
351         try {
352             StatusBarManagerInternal service = LocalServices.getService(
353                     StatusBarManagerInternal.class);
354             if (service.showShutdownUi(mReboot, mReason)) {
355                 // Sysui will handle shutdown UI.
356                 Log.d(TAG, "SysUI handling shutdown UI");
357                 return true;
358             }
359         } catch (Exception e) {
360             // If anything went wrong, ignore it and use fallback ui
361         }
362         Log.d(TAG, "SysUI is unavailable");
363         return false;
364     }
365 
beginShutdownSequence(Context context)366     private static void beginShutdownSequence(Context context) {
367         synchronized (sIsStartedGuard) {
368             if (sIsStarted) {
369                 Log.d(TAG, "Shutdown sequence already running, returning.");
370                 return;
371             }
372             sIsStarted = true;
373         }
374 
375         sInstance.mProgressDialog = showShutdownDialog(context);
376         sInstance.mContext = context;
377         sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
378 
379         // make sure we never fall asleep again
380         sInstance.mCpuWakeLock = null;
381         try {
382             sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
383                     PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
384             sInstance.mCpuWakeLock.setReferenceCounted(false);
385             sInstance.mCpuWakeLock.acquire();
386         } catch (SecurityException e) {
387             Log.w(TAG, "No permission to acquire wake lock", e);
388             sInstance.mCpuWakeLock = null;
389         }
390 
391         // also make sure the screen stays on for better user experience
392         sInstance.mScreenWakeLock = null;
393         if (sInstance.mPowerManager.isScreenOn()) {
394             try {
395                 sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
396                         PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
397                 sInstance.mScreenWakeLock.setReferenceCounted(false);
398                 sInstance.mScreenWakeLock.acquire();
399             } catch (SecurityException e) {
400                 Log.w(TAG, "No permission to acquire wake lock", e);
401                 sInstance.mScreenWakeLock = null;
402             }
403         }
404 
405         if (SecurityLog.isLoggingEnabled()) {
406             SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);
407         }
408 
409         // start the thread that initiates shutdown
410         sInstance.mHandler = new Handler() {
411         };
412         sInstance.start();
413     }
414 
actionDone()415     void actionDone() {
416         synchronized (mActionDoneSync) {
417             mActionDone = true;
418             mActionDoneSync.notifyAll();
419         }
420     }
421 
422     /**
423      * Makes sure we handle the shutdown gracefully.
424      * Shuts off power regardless of radio state if the allotted time has passed.
425      */
run()426     public void run() {
427         TimingsTraceLog shutdownTimingLog = newTimingsLog();
428         shutdownTimingLog.traceBegin("SystemServerShutdown");
429         metricShutdownStart();
430         metricStarted(METRIC_SYSTEM_SERVER);
431 
432         // Start dumping check points for this shutdown in a separate thread.
433         Thread dumpCheckPointsThread = ShutdownCheckPoints.newDumpThread(
434                 new File(CHECK_POINTS_FILE_BASENAME));
435         dumpCheckPointsThread.start();
436 
437         BroadcastReceiver br = new BroadcastReceiver() {
438             @Override public void onReceive(Context context, Intent intent) {
439                 // We don't allow apps to cancel this, so ignore the result.
440                 actionDone();
441             }
442         };
443 
444         /*
445          * Write a system property in case the system_server reboots before we
446          * get to the actual hardware restart. If that happens, we'll retry at
447          * the beginning of the SystemServer startup.
448          */
449         {
450             String reason = (mReboot ? "1" : "0") + (mReason != null ? mReason : "");
451             SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
452         }
453 
454         /*
455          * If we are rebooting into safe mode, write a system property
456          * indicating so.
457          */
458         if (mRebootSafeMode) {
459             SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
460         }
461 
462         shutdownTimingLog.traceBegin("DumpPreRebootInfo");
463         try {
464             Slog.i(TAG, "Logging pre-reboot information...");
465             PreRebootLogger.log(mContext);
466         } catch (Exception e) {
467             Slog.e(TAG, "Failed to log pre-reboot information", e);
468         }
469         shutdownTimingLog.traceEnd(); // DumpPreRebootInfo
470 
471         metricStarted(METRIC_SEND_BROADCAST);
472         shutdownTimingLog.traceBegin("SendShutdownBroadcast");
473         Log.i(TAG, "Sending shutdown broadcast...");
474 
475         // First send the high-level shut down broadcast.
476         mActionDone = false;
477         Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
478         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
479         mContext.sendOrderedBroadcastAsUser(intent,
480                 UserHandle.ALL, null, br, mHandler, 0, null, null);
481 
482         final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
483         synchronized (mActionDoneSync) {
484             while (!mActionDone) {
485                 long delay = endTime - SystemClock.elapsedRealtime();
486                 if (delay <= 0) {
487                     Log.w(TAG, "Shutdown broadcast timed out");
488                     break;
489                 } else if (mRebootHasProgressBar) {
490                     int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 *
491                             BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME);
492                     sInstance.setRebootProgress(status, null);
493                 }
494                 try {
495                     mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));
496                 } catch (InterruptedException e) {
497                 }
498             }
499         }
500         if (mRebootHasProgressBar) {
501             sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);
502         }
503         shutdownTimingLog.traceEnd(); // SendShutdownBroadcast
504         metricEnded(METRIC_SEND_BROADCAST);
505 
506         Log.i(TAG, "Shutting down activity manager...");
507         shutdownTimingLog.traceBegin("ShutdownActivityManager");
508         metricStarted(METRIC_AM);
509 
510         final IActivityManager am =
511                 IActivityManager.Stub.asInterface(ServiceManager.checkService("activity"));
512         if (am != null) {
513             try {
514                 am.shutdown(MAX_BROADCAST_TIME);
515             } catch (RemoteException e) {
516             }
517         }
518         if (mRebootHasProgressBar) {
519             sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
520         }
521         shutdownTimingLog.traceEnd();// ShutdownActivityManager
522         metricEnded(METRIC_AM);
523 
524         Log.i(TAG, "Shutting down package manager...");
525         shutdownTimingLog.traceBegin("ShutdownPackageManager");
526         metricStarted(METRIC_PM);
527 
528         final PackageManagerService pm = (PackageManagerService)
529             ServiceManager.getService("package");
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