ShutdownThread.java revision e21a4ac09d2473becaea43a73d19e9e836e7732a
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 bluetooth.disable(false); // disable but don't persist new state 344 } 345 } catch (RemoteException ex) { 346 Log.e(TAG, "RemoteException during bluetooth shutdown", ex); 347 bluetoothOff = true; 348 } 349 350 try { 351 radioOff = phone == null || !phone.isRadioOn(); 352 if (!radioOff) { 353 Log.w(TAG, "Turning off radio..."); 354 phone.setRadio(false); 355 } 356 } catch (RemoteException ex) { 357 Log.e(TAG, "RemoteException during radio shutdown", ex); 358 radioOff = true; 359 } 360 361 Log.i(TAG, "Waiting for Bluetooth and Radio..."); 362 363 // Wait a max of 32 seconds for clean shutdown 364 for (int i = 0; i < MAX_NUM_PHONE_STATE_READS; i++) { 365 if (!bluetoothOff) { 366 try { 367 bluetoothOff = 368 bluetooth.getState() == BluetoothAdapter.STATE_OFF; 369 } catch (RemoteException ex) { 370 Log.e(TAG, "RemoteException during bluetooth shutdown", ex); 371 bluetoothOff = true; 372 } 373 } 374 if (!radioOff) { 375 try { 376 radioOff = !phone.isRadioOn(); 377 } catch (RemoteException ex) { 378 Log.e(TAG, "RemoteException during radio shutdown", ex); 379 radioOff = true; 380 } 381 } 382 if (radioOff && bluetoothOff) { 383 Log.i(TAG, "Radio and Bluetooth shutdown complete."); 384 break; 385 } 386 SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC); 387 } 388 389 // Shutdown MountService to ensure media is in a safe state 390 IMountShutdownObserver observer = new IMountShutdownObserver.Stub() { 391 public void onShutDownComplete(int statusCode) throws RemoteException { 392 Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown"); 393 actionDone(); 394 } 395 }; 396 397 Log.i(TAG, "Shutting down MountService"); 398 399 // Set initial variables and time out time. 400 mActionDone = false; 401 final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME; 402 synchronized (mActionDoneSync) { 403 try { 404 final IMountService mount = IMountService.Stub.asInterface( 405 ServiceManager.checkService("mount")); 406 if (mount != null) { 407 mount.shutdown(observer); 408 } else { 409 Log.w(TAG, "MountService unavailable for shutdown"); 410 } 411 } catch (Exception e) { 412 Log.e(TAG, "Exception during MountService shutdown", e); 413 } 414 while (!mActionDone) { 415 long delay = endShutTime - SystemClock.elapsedRealtime(); 416 if (delay <= 0) { 417 Log.w(TAG, "Shutdown wait timed out"); 418 break; 419 } 420 try { 421 mActionDoneSync.wait(delay); 422 } catch (InterruptedException e) { 423 } 424 } 425 } 426 427 rebootOrShutdown(mReboot, mRebootReason); 428 } 429 430 private void shutdownRadios(int timeout) { 431 // If a radio is wedged, disabling it may hang so we do this work in another thread, 432 // just in case. 433 final long endTime = SystemClock.elapsedRealtime() + timeout; 434 final boolean[] done = new boolean[1]; 435 Thread t = new Thread() { 436 public void run() { 437 boolean nfcOff; 438 boolean bluetoothOff; 439 boolean radioOff; 440 441 final INfcAdapter nfc = 442 INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc")); 443 final ITelephony phone = 444 ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); 445 final IBluetooth bluetooth = 446 IBluetooth.Stub.asInterface(ServiceManager.checkService( 447 BluetoothAdapter.BLUETOOTH_SERVICE)); 448 449 try { 450 nfcOff = nfc == null || 451 nfc.getState() == NfcAdapter.STATE_OFF; 452 if (!nfcOff) { 453 Log.w(TAG, "Turning off NFC..."); 454 nfc.disable(false); // Don't persist new state 455 } 456 } catch (RemoteException ex) { 457 Log.e(TAG, "RemoteException during NFC shutdown", ex); 458 nfcOff = true; 459 } 460 461 try { 462 bluetoothOff = bluetooth == null || 463 bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF; 464 if (!bluetoothOff) { 465 Log.w(TAG, "Disabling Bluetooth..."); 466 bluetooth.disable(false); // disable but don't persist new state 467 } 468 } catch (RemoteException ex) { 469 Log.e(TAG, "RemoteException during bluetooth shutdown", ex); 470 bluetoothOff = true; 471 } 472 473 try { 474 radioOff = phone == null || !phone.isRadioOn(); 475 if (!radioOff) { 476 Log.w(TAG, "Turning off radio..."); 477 phone.setRadio(false); 478 } 479 } catch (RemoteException ex) { 480 Log.e(TAG, "RemoteException during radio shutdown", ex); 481 radioOff = true; 482 } 483 484 Log.i(TAG, "Waiting for NFC, Bluetooth and Radio..."); 485 486 while (SystemClock.elapsedRealtime() < endTime) { 487 if (!bluetoothOff) { 488 try { 489 bluetoothOff = 490 bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF; 491 } catch (RemoteException ex) { 492 Log.e(TAG, "RemoteException during bluetooth shutdown", ex); 493 bluetoothOff = true; 494 } 495 if (bluetoothOff) { 496 Log.i(TAG, "Bluetooth turned off."); 497 } 498 } 499 if (!radioOff) { 500 try { 501 radioOff = !phone.isRadioOn(); 502 } catch (RemoteException ex) { 503 Log.e(TAG, "RemoteException during radio shutdown", ex); 504 radioOff = true; 505 } 506 if (radioOff) { 507 Log.i(TAG, "Radio turned off."); 508 } 509 } 510 if (!nfcOff) { 511 try { 512 nfcOff = nfc.getState() == NfcAdapter.STATE_OFF; 513 } catch (RemoteException ex) { 514 Log.e(TAG, "RemoteException during NFC shutdown", ex); 515 nfcOff = true; 516 } 517 if (radioOff) { 518 Log.i(TAG, "NFC turned off."); 519 } 520 } 521 522 if (radioOff && bluetoothOff && nfcOff) { 523 Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete."); 524 done[0] = true; 525 break; 526 } 527 SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC); 528 } 529 } 530 }; 531 532 t.start(); 533 try { 534 t.join(timeout); 535 } catch (InterruptedException ex) { 536 } 537 if (!done[0]) { 538 Log.w(TAG, "Timed out waiting for NFC, Radio and Bluetooth shutdown."); 539 } 540 } 541 542 /** 543 * Do not call this directly. Use {@link #reboot(Context, String, boolean)} 544 * or {@link #shutdown(Context, boolean)} instead. 545 * 546 * @param reboot true to reboot or false to shutdown 547 * @param reason reason for reboot 548 */ 549 public static void rebootOrShutdown(boolean reboot, String reason) { 550 if (reboot) { 551 Log.i(TAG, "Rebooting, reason: " + reason); 552 try { 553 PowerManagerService.lowLevelReboot(reason); 554 } catch (Exception e) { 555 Log.e(TAG, "Reboot failed, will attempt shutdown instead", e); 556 } 557 } else if (SHUTDOWN_VIBRATE_MS > 0) { 558 // vibrate before shutting down 559 Vibrator vibrator = new SystemVibrator(); 560 try { 561 vibrator.vibrate(SHUTDOWN_VIBRATE_MS); 562 } catch (Exception e) { 563 // Failure to vibrate shouldn't interrupt shutdown. Just log it. 564 Log.w(TAG, "Failed to vibrate during shutdown.", e); 565 } 566 567 // vibrator is asynchronous so we need to wait to avoid shutting down too soon. 568 try { 569 Thread.sleep(SHUTDOWN_VIBRATE_MS); 570 } catch (InterruptedException unused) { 571 } 572 } 573 574 // Shutdown power 575 Log.i(TAG, "Performing low-level shutdown..."); 576 PowerManagerService.lowLevelShutdown(); 577 } 578} 579