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