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