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