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