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