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 ShutdownThread()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 */ shutdown(final Context context, boolean confirm)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 */ reboot(final Context context, String reason, boolean confirm)132 public static void reboot(final Context context, String reason, boolean confirm) { 133 mReboot = true; 134 mRebootReason = reason; 135 shutdown(context, confirm); 136 } 137 beginShutdownSequence(Context context)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 actionDone()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 */ run()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 */ rebootOrShutdown(boolean reboot, String reason)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