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.ActivityManagerNative;
     21 import android.app.AlertDialog;
     22 import android.app.Dialog;
     23 import android.app.IActivityManager;
     24 import android.app.ProgressDialog;
     25 import android.bluetooth.BluetoothAdapter;
     26 import android.bluetooth.IBluetoothManager;
     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.Handler;
     35 import android.os.PowerManager;
     36 import android.os.RemoteException;
     37 import android.os.ServiceManager;
     38 import android.os.SystemClock;
     39 import android.os.SystemProperties;
     40 import android.os.UserHandle;
     41 import android.os.Vibrator;
     42 import android.os.SystemVibrator;
     43 import android.os.storage.IMountService;
     44 import android.os.storage.IMountShutdownObserver;
     45 
     46 import com.android.internal.telephony.ITelephony;
     47 
     48 import android.util.Log;
     49 import android.view.WindowManager;
     50 
     51 public final class ShutdownThread extends Thread {
     52     // constants
     53     private static final String TAG = "ShutdownThread";
     54     private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
     55     // maximum time we wait for the shutdown broadcast before going on.
     56     private static final int MAX_BROADCAST_TIME = 10*1000;
     57     private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
     58     private static final int MAX_RADIO_WAIT_TIME = 12*1000;
     59 
     60     // length of vibration before shutting down
     61     private static final int SHUTDOWN_VIBRATE_MS = 500;
     62 
     63     // state tracking
     64     private static Object sIsStartedGuard = new Object();
     65     private static boolean sIsStarted = false;
     66 
     67     private static boolean mReboot;
     68     private static boolean mRebootSafeMode;
     69     private static String mRebootReason;
     70 
     71     // Provides shutdown assurance in case the system_server is killed
     72     public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested";
     73 
     74     // Indicates whether we are rebooting into safe mode
     75     public static final String REBOOT_SAFEMODE_PROPERTY = "persist.sys.safemode";
     76 
     77     // static instance of this thread
     78     private static final ShutdownThread sInstance = new ShutdownThread();
     79 
     80     private final Object mActionDoneSync = new Object();
     81     private boolean mActionDone;
     82     private Context mContext;
     83     private PowerManager mPowerManager;
     84     private PowerManager.WakeLock mCpuWakeLock;
     85     private PowerManager.WakeLock mScreenWakeLock;
     86     private Handler mHandler;
     87 
     88     private static AlertDialog sConfirmDialog;
     89 
     90     private ShutdownThread() {
     91     }
     92 
     93     /**
     94      * Request a clean shutdown, waiting for subsystems to clean up their
     95      * state etc.  Must be called from a Looper thread in which its UI
     96      * is shown.
     97      *
     98      * @param context Context used to display the shutdown progress dialog.
     99      * @param confirm true if user confirmation is needed before shutting down.
    100      */
    101     public static void shutdown(final Context context, boolean confirm) {
    102         mReboot = false;
    103         mRebootSafeMode = false;
    104         shutdownInner(context, confirm);
    105     }
    106 
    107     static void shutdownInner(final Context context, boolean confirm) {
    108         // ensure that only one thread is trying to power down.
    109         // any additional calls are just returned
    110         synchronized (sIsStartedGuard) {
    111             if (sIsStarted) {
    112                 Log.d(TAG, "Request to shutdown already running, returning.");
    113                 return;
    114             }
    115         }
    116 
    117         final int longPressBehavior = context.getResources().getInteger(
    118                         com.android.internal.R.integer.config_longPressOnPowerBehavior);
    119         final int resourceId = mRebootSafeMode
    120                 ? com.android.internal.R.string.reboot_safemode_confirm
    121                 : (longPressBehavior == 2
    122                         ? com.android.internal.R.string.shutdown_confirm_question
    123                         : com.android.internal.R.string.shutdown_confirm);
    124 
    125         Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
    126 
    127         if (confirm) {
    128             final CloseDialogReceiver closer = new CloseDialogReceiver(context);
    129             if (sConfirmDialog != null) {
    130                 sConfirmDialog.dismiss();
    131             }
    132             sConfirmDialog = new AlertDialog.Builder(context)
    133                     .setTitle(mRebootSafeMode
    134                             ? com.android.internal.R.string.reboot_safemode_title
    135                             : com.android.internal.R.string.power_off)
    136                     .setMessage(resourceId)
    137                     .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
    138                         public void onClick(DialogInterface dialog, int which) {
    139                             beginShutdownSequence(context);
    140                         }
    141                     })
    142                     .setNegativeButton(com.android.internal.R.string.no, null)
    143                     .create();
    144             closer.dialog = sConfirmDialog;
    145             sConfirmDialog.setOnDismissListener(closer);
    146             sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
    147             sConfirmDialog.show();
    148         } else {
    149             beginShutdownSequence(context);
    150         }
    151     }
    152 
    153     private static class CloseDialogReceiver extends BroadcastReceiver
    154             implements DialogInterface.OnDismissListener {
    155         private Context mContext;
    156         public Dialog dialog;
    157 
    158         CloseDialogReceiver(Context context) {
    159             mContext = context;
    160             IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
    161             context.registerReceiver(this, filter);
    162         }
    163 
    164         @Override
    165         public void onReceive(Context context, Intent intent) {
    166             dialog.cancel();
    167         }
    168 
    169         public void onDismiss(DialogInterface unused) {
    170             mContext.unregisterReceiver(this);
    171         }
    172     }
    173 
    174     /**
    175      * Request a clean shutdown, waiting for subsystems to clean up their
    176      * state etc.  Must be called from a Looper thread in which its UI
    177      * is shown.
    178      *
    179      * @param context Context used to display the shutdown progress dialog.
    180      * @param reason code to pass to the kernel (e.g. "recovery"), or null.
    181      * @param confirm true if user confirmation is needed before shutting down.
    182      */
    183     public static void reboot(final Context context, String reason, boolean confirm) {
    184         mReboot = true;
    185         mRebootSafeMode = false;
    186         mRebootReason = reason;
    187         shutdownInner(context, confirm);
    188     }
    189 
    190     /**
    191      * Request a reboot into safe mode.  Must be called from a Looper thread in which its UI
    192      * is shown.
    193      *
    194      * @param context Context used to display the shutdown progress dialog.
    195      * @param confirm true if user confirmation is needed before shutting down.
    196      */
    197     public static void rebootSafeMode(final Context context, boolean confirm) {
    198         mReboot = true;
    199         mRebootSafeMode = true;
    200         mRebootReason = null;
    201         shutdownInner(context, confirm);
    202     }
    203 
    204     private static void beginShutdownSequence(Context context) {
    205         synchronized (sIsStartedGuard) {
    206             if (sIsStarted) {
    207                 Log.d(TAG, "Shutdown sequence already running, returning.");
    208                 return;
    209             }
    210             sIsStarted = true;
    211         }
    212 
    213         // throw up an indeterminate system dialog to indicate radio is
    214         // shutting down.
    215         ProgressDialog pd = new ProgressDialog(context);
    216         pd.setTitle(context.getText(com.android.internal.R.string.power_off));
    217         pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
    218         pd.setIndeterminate(true);
    219         pd.setCancelable(false);
    220         pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
    221 
    222         pd.show();
    223 
    224         sInstance.mContext = context;
    225         sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
    226 
    227         // make sure we never fall asleep again
    228         sInstance.mCpuWakeLock = null;
    229         try {
    230             sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
    231                     PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
    232             sInstance.mCpuWakeLock.setReferenceCounted(false);
    233             sInstance.mCpuWakeLock.acquire();
    234         } catch (SecurityException e) {
    235             Log.w(TAG, "No permission to acquire wake lock", e);
    236             sInstance.mCpuWakeLock = null;
    237         }
    238 
    239         // also make sure the screen stays on for better user experience
    240         sInstance.mScreenWakeLock = null;
    241         if (sInstance.mPowerManager.isScreenOn()) {
    242             try {
    243                 sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
    244                         PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
    245                 sInstance.mScreenWakeLock.setReferenceCounted(false);
    246                 sInstance.mScreenWakeLock.acquire();
    247             } catch (SecurityException e) {
    248                 Log.w(TAG, "No permission to acquire wake lock", e);
    249                 sInstance.mScreenWakeLock = null;
    250             }
    251         }
    252 
    253         // start the thread that initiates shutdown
    254         sInstance.mHandler = new Handler() {
    255         };
    256         sInstance.start();
    257     }
    258 
    259     void actionDone() {
    260         synchronized (mActionDoneSync) {
    261             mActionDone = true;
    262             mActionDoneSync.notifyAll();
    263         }
    264     }
    265 
    266     /**
    267      * Makes sure we handle the shutdown gracefully.
    268      * Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
    269      */
    270     public void run() {
    271         BroadcastReceiver br = new BroadcastReceiver() {
    272             @Override public void onReceive(Context context, Intent intent) {
    273                 // We don't allow apps to cancel this, so ignore the result.
    274                 actionDone();
    275             }
    276         };
    277 
    278         /*
    279          * Write a system property in case the system_server reboots before we
    280          * get to the actual hardware restart. If that happens, we'll retry at
    281          * the beginning of the SystemServer startup.
    282          */
    283         {
    284             String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : "");
    285             SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
    286         }
    287 
    288         /*
    289          * If we are rebooting into safe mode, write a system property
    290          * indicating so.
    291          */
    292         if (mRebootSafeMode) {
    293             SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
    294         }
    295 
    296         Log.i(TAG, "Sending shutdown broadcast...");
    297 
    298         // First send the high-level shut down broadcast.
    299         mActionDone = false;
    300         Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
    301         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
    302         mContext.sendOrderedBroadcastAsUser(intent,
    303                 UserHandle.ALL, null, br, mHandler, 0, null, null);
    304 
    305         final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
    306         synchronized (mActionDoneSync) {
    307             while (!mActionDone) {
    308                 long delay = endTime - SystemClock.elapsedRealtime();
    309                 if (delay <= 0) {
    310                     Log.w(TAG, "Shutdown broadcast timed out");
    311                     break;
    312                 }
    313                 try {
    314                     mActionDoneSync.wait(delay);
    315                 } catch (InterruptedException e) {
    316                 }
    317             }
    318         }
    319 
    320         Log.i(TAG, "Shutting down activity manager...");
    321 
    322         final IActivityManager am =
    323             ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
    324         if (am != null) {
    325             try {
    326                 am.shutdown(MAX_BROADCAST_TIME);
    327             } catch (RemoteException e) {
    328             }
    329         }
    330 
    331         // Shutdown radios.
    332         shutdownRadios(MAX_RADIO_WAIT_TIME);
    333 
    334         // Shutdown MountService to ensure media is in a safe state
    335         IMountShutdownObserver observer = new IMountShutdownObserver.Stub() {
    336             public void onShutDownComplete(int statusCode) throws RemoteException {
    337                 Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown");
    338                 actionDone();
    339             }
    340         };
    341 
    342         Log.i(TAG, "Shutting down MountService");
    343 
    344         // Set initial variables and time out time.
    345         mActionDone = false;
    346         final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
    347         synchronized (mActionDoneSync) {
    348             try {
    349                 final IMountService mount = IMountService.Stub.asInterface(
    350                         ServiceManager.checkService("mount"));
    351                 if (mount != null) {
    352                     mount.shutdown(observer);
    353                 } else {
    354                     Log.w(TAG, "MountService unavailable for shutdown");
    355                 }
    356             } catch (Exception e) {
    357                 Log.e(TAG, "Exception during MountService shutdown", e);
    358             }
    359             while (!mActionDone) {
    360                 long delay = endShutTime - SystemClock.elapsedRealtime();
    361                 if (delay <= 0) {
    362                     Log.w(TAG, "Shutdown wait timed out");
    363                     break;
    364                 }
    365                 try {
    366                     mActionDoneSync.wait(delay);
    367                 } catch (InterruptedException e) {
    368                 }
    369             }
    370         }
    371 
    372         rebootOrShutdown(mReboot, mRebootReason);
    373     }
    374 
    375     private void shutdownRadios(int timeout) {
    376         // If a radio is wedged, disabling it may hang so we do this work in another thread,
    377         // just in case.
    378         final long endTime = SystemClock.elapsedRealtime() + timeout;
    379         final boolean[] done = new boolean[1];
    380         Thread t = new Thread() {
    381             public void run() {
    382                 boolean nfcOff;
    383                 boolean bluetoothOff;
    384                 boolean radioOff;
    385 
    386                 final INfcAdapter nfc =
    387                         INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc"));
    388                 final ITelephony phone =
    389                         ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
    390                 final IBluetoothManager bluetooth =
    391                         IBluetoothManager.Stub.asInterface(ServiceManager.checkService(
    392                                 BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE));
    393 
    394                 try {
    395                     nfcOff = nfc == null ||
    396                              nfc.getState() == NfcAdapter.STATE_OFF;
    397                     if (!nfcOff) {
    398                         Log.w(TAG, "Turning off NFC...");
    399                         nfc.disable(false); // Don't persist new state
    400                     }
    401                 } catch (RemoteException ex) {
    402                 Log.e(TAG, "RemoteException during NFC shutdown", ex);
    403                     nfcOff = true;
    404                 }
    405 
    406                 try {
    407                     bluetoothOff = bluetooth == null || !bluetooth.isEnabled();
    408                     if (!bluetoothOff) {
    409                         Log.w(TAG, "Disabling Bluetooth...");
    410                         bluetooth.disable(false);  // disable but don't persist new state
    411                     }
    412                 } catch (RemoteException ex) {
    413                     Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
    414                     bluetoothOff = true;
    415                 }
    416 
    417                 try {
    418                     radioOff = phone == null || !phone.isRadioOn();
    419                     if (!radioOff) {
    420                         Log.w(TAG, "Turning off radio...");
    421                         phone.setRadio(false);
    422                     }
    423                 } catch (RemoteException ex) {
    424                     Log.e(TAG, "RemoteException during radio shutdown", ex);
    425                     radioOff = true;
    426                 }
    427 
    428                 Log.i(TAG, "Waiting for NFC, Bluetooth and Radio...");
    429 
    430                 while (SystemClock.elapsedRealtime() < endTime) {
    431                     if (!bluetoothOff) {
    432                         try {
    433                             bluetoothOff = !bluetooth.isEnabled();
    434                         } catch (RemoteException ex) {
    435                             Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
    436                             bluetoothOff = true;
    437                         }
    438                         if (bluetoothOff) {
    439                             Log.i(TAG, "Bluetooth turned off.");
    440                         }
    441                     }
    442                     if (!radioOff) {
    443                         try {
    444                             radioOff = !phone.isRadioOn();
    445                         } catch (RemoteException ex) {
    446                             Log.e(TAG, "RemoteException during radio shutdown", ex);
    447                             radioOff = true;
    448                         }
    449                         if (radioOff) {
    450                             Log.i(TAG, "Radio turned off.");
    451                         }
    452                     }
    453                     if (!nfcOff) {
    454                         try {
    455                             nfcOff = nfc.getState() == NfcAdapter.STATE_OFF;
    456                         } catch (RemoteException ex) {
    457                             Log.e(TAG, "RemoteException during NFC shutdown", ex);
    458                             nfcOff = true;
    459                         }
    460                         if (radioOff) {
    461                             Log.i(TAG, "NFC turned off.");
    462                         }
    463                     }
    464 
    465                     if (radioOff && bluetoothOff && nfcOff) {
    466                         Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete.");
    467                         done[0] = true;
    468                         break;
    469                     }
    470                     SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
    471                 }
    472             }
    473         };
    474 
    475         t.start();
    476         try {
    477             t.join(timeout);
    478         } catch (InterruptedException ex) {
    479         }
    480         if (!done[0]) {
    481             Log.w(TAG, "Timed out waiting for NFC, Radio and Bluetooth shutdown.");
    482         }
    483     }
    484 
    485     /**
    486      * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
    487      * or {@link #shutdown(Context, boolean)} instead.
    488      *
    489      * @param reboot true to reboot or false to shutdown
    490      * @param reason reason for reboot
    491      */
    492     public static void rebootOrShutdown(boolean reboot, String reason) {
    493         if (reboot) {
    494             Log.i(TAG, "Rebooting, reason: " + reason);
    495             PowerManagerService.lowLevelReboot(reason);
    496             Log.e(TAG, "Reboot failed, will attempt shutdown instead");
    497         } else if (SHUTDOWN_VIBRATE_MS > 0) {
    498             // vibrate before shutting down
    499             Vibrator vibrator = new SystemVibrator();
    500             try {
    501                 vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
    502             } catch (Exception e) {
    503                 // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
    504                 Log.w(TAG, "Failed to vibrate during shutdown.", e);
    505             }
    506 
    507             // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
    508             try {
    509                 Thread.sleep(SHUTDOWN_VIBRATE_MS);
    510             } catch (InterruptedException unused) {
    511             }
    512         }
    513 
    514         // Shutdown power
    515         Log.i(TAG, "Performing low-level shutdown...");
    516         PowerManagerService.lowLevelShutdown();
    517     }
    518 }
    519