ShutdownThread.java revision 9bb765448df43d41e0a3edb7de1d1641c9251c35
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.AudioAttributes; 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.UserManager; 43import android.os.Vibrator; 44import android.os.SystemVibrator; 45import android.os.storage.IMountService; 46import android.os.storage.IMountShutdownObserver; 47import android.system.ErrnoException; 48import android.system.Os; 49 50import com.android.internal.telephony.ITelephony; 51import com.android.server.pm.PackageManagerService; 52 53import android.util.Log; 54import android.view.WindowManager; 55 56import java.io.BufferedReader; 57import java.io.File; 58import java.io.FileReader; 59import java.io.IOException; 60 61public final class ShutdownThread extends Thread { 62 // constants 63 private static final String TAG = "ShutdownThread"; 64 private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500; 65 // maximum time we wait for the shutdown broadcast before going on. 66 private static final int MAX_BROADCAST_TIME = 10*1000; 67 private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000; 68 private static final int MAX_RADIO_WAIT_TIME = 12*1000; 69 private static final int MAX_UNCRYPT_WAIT_TIME = 15*60*1000; 70 71 // length of vibration before shutting down 72 private static final int SHUTDOWN_VIBRATE_MS = 500; 73 74 // state tracking 75 private static Object sIsStartedGuard = new Object(); 76 private static boolean sIsStarted = false; 77 78 // uncrypt status file 79 private static final String UNCRYPT_STATUS_FILE = "/cache/recovery/uncrypt_status"; 80 81 private static boolean mReboot; 82 private static boolean mRebootSafeMode; 83 private static String mRebootReason; 84 85 // Provides shutdown assurance in case the system_server is killed 86 public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested"; 87 88 // Indicates whether we are rebooting into safe mode 89 public static final String REBOOT_SAFEMODE_PROPERTY = "persist.sys.safemode"; 90 91 // static instance of this thread 92 private static final ShutdownThread sInstance = new ShutdownThread(); 93 94 private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() 95 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 96 .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) 97 .build(); 98 99 private final Object mActionDoneSync = new Object(); 100 private boolean mActionDone; 101 private Context mContext; 102 private PowerManager mPowerManager; 103 private PowerManager.WakeLock mCpuWakeLock; 104 private PowerManager.WakeLock mScreenWakeLock; 105 private Handler mHandler; 106 107 private static AlertDialog sConfirmDialog; 108 private ProgressDialog mProgressDialog; 109 110 private ShutdownThread() { 111 } 112 113 /** 114 * Request a clean shutdown, waiting for subsystems to clean up their 115 * state etc. Must be called from a Looper thread in which its UI 116 * is shown. 117 * 118 * @param context Context used to display the shutdown progress dialog. 119 * @param confirm true if user confirmation is needed before shutting down. 120 */ 121 public static void shutdown(final Context context, boolean confirm) { 122 mReboot = false; 123 mRebootSafeMode = false; 124 shutdownInner(context, confirm); 125 } 126 127 static void shutdownInner(final Context context, boolean confirm) { 128 // ensure that only one thread is trying to power down. 129 // any additional calls are just returned 130 synchronized (sIsStartedGuard) { 131 if (sIsStarted) { 132 Log.d(TAG, "Request to shutdown already running, returning."); 133 return; 134 } 135 } 136 137 final int longPressBehavior = context.getResources().getInteger( 138 com.android.internal.R.integer.config_longPressOnPowerBehavior); 139 final int resourceId = mRebootSafeMode 140 ? com.android.internal.R.string.reboot_safemode_confirm 141 : (longPressBehavior == 2 142 ? com.android.internal.R.string.shutdown_confirm_question 143 : com.android.internal.R.string.shutdown_confirm); 144 145 Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior); 146 147 if (confirm) { 148 final CloseDialogReceiver closer = new CloseDialogReceiver(context); 149 if (sConfirmDialog != null) { 150 sConfirmDialog.dismiss(); 151 } 152 sConfirmDialog = new AlertDialog.Builder(context) 153 .setTitle(mRebootSafeMode 154 ? com.android.internal.R.string.reboot_safemode_title 155 : com.android.internal.R.string.power_off) 156 .setMessage(resourceId) 157 .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() { 158 public void onClick(DialogInterface dialog, int which) { 159 beginShutdownSequence(context); 160 } 161 }) 162 .setNegativeButton(com.android.internal.R.string.no, null) 163 .create(); 164 closer.dialog = sConfirmDialog; 165 sConfirmDialog.setOnDismissListener(closer); 166 sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 167 sConfirmDialog.show(); 168 } else { 169 beginShutdownSequence(context); 170 } 171 } 172 173 private static class CloseDialogReceiver extends BroadcastReceiver 174 implements DialogInterface.OnDismissListener { 175 private Context mContext; 176 public Dialog dialog; 177 178 CloseDialogReceiver(Context context) { 179 mContext = context; 180 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 181 context.registerReceiver(this, filter); 182 } 183 184 @Override 185 public void onReceive(Context context, Intent intent) { 186 dialog.cancel(); 187 } 188 189 public void onDismiss(DialogInterface unused) { 190 mContext.unregisterReceiver(this); 191 } 192 } 193 194 /** 195 * Request a clean shutdown, waiting for subsystems to clean up their 196 * state etc. Must be called from a Looper thread in which its UI 197 * is shown. 198 * 199 * @param context Context used to display the shutdown progress dialog. 200 * @param reason code to pass to the kernel (e.g. "recovery"), or null. 201 * @param confirm true if user confirmation is needed before shutting down. 202 */ 203 public static void reboot(final Context context, String reason, boolean confirm) { 204 mReboot = true; 205 mRebootSafeMode = false; 206 mRebootReason = reason; 207 shutdownInner(context, confirm); 208 } 209 210 /** 211 * Request a reboot into safe mode. Must be called from a Looper thread in which its UI 212 * is shown. 213 * 214 * @param context Context used to display the shutdown progress dialog. 215 * @param confirm true if user confirmation is needed before shutting down. 216 */ 217 public static void rebootSafeMode(final Context context, boolean confirm) { 218 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 219 if (um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { 220 return; 221 } 222 223 mReboot = true; 224 mRebootSafeMode = true; 225 mRebootReason = null; 226 shutdownInner(context, confirm); 227 } 228 229 private static void beginShutdownSequence(Context context) { 230 synchronized (sIsStartedGuard) { 231 if (sIsStarted) { 232 Log.d(TAG, "Shutdown sequence already running, returning."); 233 return; 234 } 235 sIsStarted = true; 236 } 237 238 // throw up an indeterminate system dialog to indicate radio is 239 // shutting down. 240 ProgressDialog pd = new ProgressDialog(context); 241 if (PowerManager.REBOOT_RECOVERY.equals(mRebootReason)) { 242 pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_recovery_title)); 243 } else { 244 pd.setTitle(context.getText(com.android.internal.R.string.power_off)); 245 } 246 pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress)); 247 pd.setIndeterminate(true); 248 pd.setCancelable(false); 249 pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 250 251 pd.show(); 252 253 sInstance.mProgressDialog = pd; 254 sInstance.mContext = context; 255 sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 256 257 // make sure we never fall asleep again 258 sInstance.mCpuWakeLock = null; 259 try { 260 sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock( 261 PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu"); 262 sInstance.mCpuWakeLock.setReferenceCounted(false); 263 sInstance.mCpuWakeLock.acquire(); 264 } catch (SecurityException e) { 265 Log.w(TAG, "No permission to acquire wake lock", e); 266 sInstance.mCpuWakeLock = null; 267 } 268 269 // also make sure the screen stays on for better user experience 270 sInstance.mScreenWakeLock = null; 271 if (sInstance.mPowerManager.isScreenOn()) { 272 try { 273 sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock( 274 PowerManager.FULL_WAKE_LOCK, TAG + "-screen"); 275 sInstance.mScreenWakeLock.setReferenceCounted(false); 276 sInstance.mScreenWakeLock.acquire(); 277 } catch (SecurityException e) { 278 Log.w(TAG, "No permission to acquire wake lock", e); 279 sInstance.mScreenWakeLock = null; 280 } 281 } 282 283 // start the thread that initiates shutdown 284 sInstance.mHandler = new Handler() { 285 }; 286 sInstance.start(); 287 } 288 289 void actionDone() { 290 synchronized (mActionDoneSync) { 291 mActionDone = true; 292 mActionDoneSync.notifyAll(); 293 } 294 } 295 296 /** 297 * Makes sure we handle the shutdown gracefully. 298 * Shuts off power regardless of radio and bluetooth state if the alloted time has passed. 299 */ 300 public void run() { 301 BroadcastReceiver br = new BroadcastReceiver() { 302 @Override public void onReceive(Context context, Intent intent) { 303 // We don't allow apps to cancel this, so ignore the result. 304 actionDone(); 305 } 306 }; 307 308 /* 309 * Write a system property in case the system_server reboots before we 310 * get to the actual hardware restart. If that happens, we'll retry at 311 * the beginning of the SystemServer startup. 312 */ 313 { 314 String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : ""); 315 SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason); 316 } 317 318 /* 319 * If we are rebooting into safe mode, write a system property 320 * indicating so. 321 */ 322 if (mRebootSafeMode) { 323 SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1"); 324 } 325 326 Log.i(TAG, "Sending shutdown broadcast..."); 327 328 // First send the high-level shut down broadcast. 329 mActionDone = false; 330 Intent intent = new Intent(Intent.ACTION_SHUTDOWN); 331 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 332 mContext.sendOrderedBroadcastAsUser(intent, 333 UserHandle.ALL, null, br, mHandler, 0, null, null); 334 335 final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME; 336 synchronized (mActionDoneSync) { 337 while (!mActionDone) { 338 long delay = endTime - SystemClock.elapsedRealtime(); 339 if (delay <= 0) { 340 Log.w(TAG, "Shutdown broadcast timed out"); 341 break; 342 } 343 try { 344 mActionDoneSync.wait(delay); 345 } catch (InterruptedException e) { 346 } 347 } 348 } 349 350 Log.i(TAG, "Shutting down activity manager..."); 351 352 final IActivityManager am = 353 ActivityManagerNative.asInterface(ServiceManager.checkService("activity")); 354 if (am != null) { 355 try { 356 am.shutdown(MAX_BROADCAST_TIME); 357 } catch (RemoteException e) { 358 } 359 } 360 361 Log.i(TAG, "Shutting down package manager..."); 362 363 final PackageManagerService pm = (PackageManagerService) 364 ServiceManager.getService("package"); 365 if (pm != null) { 366 pm.shutdown(); 367 } 368 369 // Shutdown radios. 370 shutdownRadios(MAX_RADIO_WAIT_TIME); 371 372 // Shutdown MountService to ensure media is in a safe state 373 IMountShutdownObserver observer = new IMountShutdownObserver.Stub() { 374 public void onShutDownComplete(int statusCode) throws RemoteException { 375 Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown"); 376 actionDone(); 377 } 378 }; 379 380 Log.i(TAG, "Shutting down MountService"); 381 382 // Set initial variables and time out time. 383 mActionDone = false; 384 final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME; 385 synchronized (mActionDoneSync) { 386 try { 387 final IMountService mount = IMountService.Stub.asInterface( 388 ServiceManager.checkService("mount")); 389 if (mount != null) { 390 mount.shutdown(observer); 391 } else { 392 Log.w(TAG, "MountService unavailable for shutdown"); 393 } 394 } catch (Exception e) { 395 Log.e(TAG, "Exception during MountService shutdown", e); 396 } 397 while (!mActionDone) { 398 long delay = endShutTime - SystemClock.elapsedRealtime(); 399 if (delay <= 0) { 400 Log.w(TAG, "Shutdown wait timed out"); 401 break; 402 } 403 try { 404 mActionDoneSync.wait(delay); 405 } catch (InterruptedException e) { 406 } 407 } 408 } 409 410 // If it's to reboot into recovery, invoke uncrypt via init service. 411 if (mRebootReason.equals(PowerManager.REBOOT_RECOVERY)) { 412 uncrypt(); 413 } 414 415 rebootOrShutdown(mContext, mReboot, mRebootReason); 416 } 417 418 private void prepareUncryptProgress() { 419 // Reset the dialog message to show the decrypt process. 420 mHandler.post(new Runnable() { 421 @Override 422 public void run() { 423 if (mProgressDialog != null) { 424 mProgressDialog.dismiss(); 425 } 426 // It doesn't work to change the style of the existing 427 // one. Have to create a new one. 428 ProgressDialog pd = new ProgressDialog(mContext); 429 430 pd.setTitle(mContext.getText( 431 com.android.internal.R.string.reboot_to_recovery_title)); 432 pd.setMessage(mContext.getText( 433 com.android.internal.R.string.reboot_to_recovery_progress)); 434 pd.setIndeterminate(false); 435 pd.setMax(100); 436 pd.setCancelable(false); 437 pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 438 pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); 439 pd.setProgressNumberFormat(null); 440 pd.setProgress(0); 441 442 mProgressDialog = pd; 443 mProgressDialog.show(); 444 } 445 }); 446 } 447 448 private void setUncryptProgress(final int progress) { 449 mHandler.post(new Runnable() { 450 @Override 451 public void run() { 452 if (mProgressDialog != null) { 453 mProgressDialog.setProgress(progress); 454 } 455 } 456 }); 457 } 458 459 private void shutdownRadios(int timeout) { 460 // If a radio is wedged, disabling it may hang so we do this work in another thread, 461 // just in case. 462 final long endTime = SystemClock.elapsedRealtime() + timeout; 463 final boolean[] done = new boolean[1]; 464 Thread t = new Thread() { 465 public void run() { 466 boolean nfcOff; 467 boolean bluetoothOff; 468 boolean radioOff; 469 470 final INfcAdapter nfc = 471 INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc")); 472 final ITelephony phone = 473 ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); 474 final IBluetoothManager bluetooth = 475 IBluetoothManager.Stub.asInterface(ServiceManager.checkService( 476 BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE)); 477 478 try { 479 nfcOff = nfc == null || 480 nfc.getState() == NfcAdapter.STATE_OFF; 481 if (!nfcOff) { 482 Log.w(TAG, "Turning off NFC..."); 483 nfc.disable(false); // Don't persist new state 484 } 485 } catch (RemoteException ex) { 486 Log.e(TAG, "RemoteException during NFC shutdown", ex); 487 nfcOff = true; 488 } 489 490 try { 491 bluetoothOff = bluetooth == null || !bluetooth.isEnabled(); 492 if (!bluetoothOff) { 493 Log.w(TAG, "Disabling Bluetooth..."); 494 bluetooth.disable(false); // disable but don't persist new state 495 } 496 } catch (RemoteException ex) { 497 Log.e(TAG, "RemoteException during bluetooth shutdown", ex); 498 bluetoothOff = true; 499 } 500 501 try { 502 radioOff = phone == null || !phone.needMobileRadioShutdown(); 503 if (!radioOff) { 504 Log.w(TAG, "Turning off cellular radios..."); 505 phone.shutdownMobileRadios(); 506 } 507 } catch (RemoteException ex) { 508 Log.e(TAG, "RemoteException during radio shutdown", ex); 509 radioOff = true; 510 } 511 512 Log.i(TAG, "Waiting for NFC, Bluetooth and Radio..."); 513 514 while (SystemClock.elapsedRealtime() < endTime) { 515 if (!bluetoothOff) { 516 try { 517 bluetoothOff = !bluetooth.isEnabled(); 518 } catch (RemoteException ex) { 519 Log.e(TAG, "RemoteException during bluetooth shutdown", ex); 520 bluetoothOff = true; 521 } 522 if (bluetoothOff) { 523 Log.i(TAG, "Bluetooth turned off."); 524 } 525 } 526 if (!radioOff) { 527 try { 528 radioOff = !phone.needMobileRadioShutdown(); 529 } catch (RemoteException ex) { 530 Log.e(TAG, "RemoteException during radio shutdown", ex); 531 radioOff = true; 532 } 533 if (radioOff) { 534 Log.i(TAG, "Radio turned off."); 535 } 536 } 537 if (!nfcOff) { 538 try { 539 nfcOff = nfc.getState() == NfcAdapter.STATE_OFF; 540 } catch (RemoteException ex) { 541 Log.e(TAG, "RemoteException during NFC shutdown", ex); 542 nfcOff = true; 543 } 544 if (nfcOff) { 545 Log.i(TAG, "NFC turned off."); 546 } 547 } 548 549 if (radioOff && bluetoothOff && nfcOff) { 550 Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete."); 551 done[0] = true; 552 break; 553 } 554 SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC); 555 } 556 } 557 }; 558 559 t.start(); 560 try { 561 t.join(timeout); 562 } catch (InterruptedException ex) { 563 } 564 if (!done[0]) { 565 Log.w(TAG, "Timed out waiting for NFC, Radio and Bluetooth shutdown."); 566 } 567 } 568 569 /** 570 * Do not call this directly. Use {@link #reboot(Context, String, boolean)} 571 * or {@link #shutdown(Context, boolean)} instead. 572 * 573 * @param context Context used to vibrate or null without vibration 574 * @param reboot true to reboot or false to shutdown 575 * @param reason reason for reboot 576 */ 577 public static void rebootOrShutdown(final Context context, boolean reboot, String reason) { 578 if (reboot) { 579 Log.i(TAG, "Rebooting, reason: " + reason); 580 PowerManagerService.lowLevelReboot(reason); 581 Log.e(TAG, "Reboot failed, will attempt shutdown instead"); 582 } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) { 583 // vibrate before shutting down 584 Vibrator vibrator = new SystemVibrator(context); 585 try { 586 vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES); 587 } catch (Exception e) { 588 // Failure to vibrate shouldn't interrupt shutdown. Just log it. 589 Log.w(TAG, "Failed to vibrate during shutdown.", e); 590 } 591 592 // vibrator is asynchronous so we need to wait to avoid shutting down too soon. 593 try { 594 Thread.sleep(SHUTDOWN_VIBRATE_MS); 595 } catch (InterruptedException unused) { 596 } 597 } 598 599 // Shutdown power 600 Log.i(TAG, "Performing low-level shutdown..."); 601 PowerManagerService.lowLevelShutdown(); 602 } 603 604 private void uncrypt() { 605 Log.i(TAG, "Calling uncrypt and monitoring the progress..."); 606 607 // Update the ProcessDialog message and style. 608 sInstance.prepareUncryptProgress(); 609 610 final boolean[] done = new boolean[1]; 611 done[0] = false; 612 Thread t = new Thread() { 613 @Override 614 public void run() { 615 // Create the status pipe file to communicate with /system/bin/uncrypt. 616 new File(UNCRYPT_STATUS_FILE).delete(); 617 try { 618 Os.mkfifo(UNCRYPT_STATUS_FILE, 0600); 619 } catch (ErrnoException e) { 620 Log.w(TAG, "ErrnoException when creating named pipe \"" + UNCRYPT_STATUS_FILE + 621 "\": " + e.getMessage()); 622 } 623 624 SystemProperties.set("ctl.start", "uncrypt"); 625 626 // Read the status from the pipe. 627 try (BufferedReader reader = new BufferedReader( 628 new FileReader(UNCRYPT_STATUS_FILE))) { 629 630 int last_status = Integer.MIN_VALUE; 631 while (true) { 632 String str = reader.readLine(); 633 try { 634 int status = Integer.parseInt(str); 635 636 // Avoid flooding the log with the same message. 637 if (status == last_status && last_status != Integer.MIN_VALUE) { 638 continue; 639 } 640 last_status = status; 641 642 if (status >= 0 && status < 100) { 643 // Update status 644 Log.d(TAG, "uncrypt read status: " + status); 645 sInstance.setUncryptProgress(status); 646 } else if (status == 100) { 647 Log.d(TAG, "uncrypt successfully finished."); 648 sInstance.setUncryptProgress(status); 649 break; 650 } else { 651 // Error in /system/bin/uncrypt. Or it's rebooting to recovery 652 // to perform other operations (e.g. factory reset). 653 Log.d(TAG, "uncrypt failed with status: " + status); 654 break; 655 } 656 } catch (NumberFormatException unused) { 657 Log.d(TAG, "uncrypt invalid status received: " + str); 658 break; 659 } 660 } 661 } catch (IOException unused) { 662 Log.w(TAG, "IOException when reading \"" + UNCRYPT_STATUS_FILE + "\"."); 663 } 664 done[0] = true; 665 } 666 }; 667 t.start(); 668 669 try { 670 t.join(MAX_UNCRYPT_WAIT_TIME); 671 } catch (InterruptedException unused) { 672 } 673 if (!done[0]) { 674 Log.w(TAG, "Timed out waiting for uncrypt."); 675 } 676 } 677} 678