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