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