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