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