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