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