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