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