blob: 77d0c97bbf62a61231cbbec907cb8299700e0e23 [file] [log] [blame]
Dianne Hackborn55280a92009-05-07 15:53:46 -07001/*
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.internal.app;
19
20import android.app.ActivityManagerNative;
Joe Onoratod208e702010-10-08 16:22:43 -040021import android.app.AlertDialog;
22import android.app.Dialog;
Dianne Hackborn55280a92009-05-07 15:53:46 -070023import android.app.IActivityManager;
24import android.app.ProgressDialog;
Nick Pellybd022f42009-08-14 18:33:38 -070025import android.bluetooth.BluetoothAdapter;
26import android.bluetooth.IBluetooth;
Dianne Hackborn55280a92009-05-07 15:53:46 -070027import android.content.BroadcastReceiver;
28import android.content.Context;
29import android.content.DialogInterface;
30import android.content.Intent;
Joe Onoratod208e702010-10-08 16:22:43 -040031import android.content.IntentFilter;
Dianne Hackborn55280a92009-05-07 15:53:46 -070032import android.os.Handler;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080033import android.os.Power;
Dianne Hackbornf99ae762010-03-08 12:43:51 -080034import android.os.PowerManager;
Dianne Hackborn55280a92009-05-07 15:53:46 -070035import android.os.RemoteException;
Dianne Hackborn55280a92009-05-07 15:53:46 -070036import android.os.ServiceManager;
37import android.os.SystemClock;
Kenny Rootf547d672010-09-22 10:36:48 -070038import android.os.SystemProperties;
Mike Lockwooda717f642010-04-01 20:01:44 -070039import android.os.Vibrator;
San Mehatb1043402010-02-05 08:26:50 -080040import android.os.storage.IMountService;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080041import android.os.storage.IMountShutdownObserver;
Dianne Hackborn568cae52009-10-07 16:13:39 -070042
Dianne Hackborn55280a92009-05-07 15:53:46 -070043import com.android.internal.telephony.ITelephony;
44import android.util.Log;
45import android.view.WindowManager;
46
47public 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;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080054 private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
Mike Lockwooda717f642010-04-01 20:01:44 -070055
56 // length of vibration before shutting down
57 private static final int SHUTDOWN_VIBRATE_MS = 500;
Dianne Hackborn55280a92009-05-07 15:53:46 -070058
59 // state tracking
60 private static Object sIsStartedGuard = new Object();
61 private static boolean sIsStarted = false;
62
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080063 private static boolean mReboot;
64 private static String mRebootReason;
65
Kenny Rootf547d672010-09-22 10:36:48 -070066 // Provides shutdown assurance in case the system_server is killed
67 public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested";
68
Dianne Hackborn55280a92009-05-07 15:53:46 -070069 // static instance of this thread
70 private static final ShutdownThread sInstance = new ShutdownThread();
71
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080072 private final Object mActionDoneSync = new Object();
73 private boolean mActionDone;
Dianne Hackborn55280a92009-05-07 15:53:46 -070074 private Context mContext;
Dianne Hackbornf99ae762010-03-08 12:43:51 -080075 private PowerManager mPowerManager;
Mattias Larssoncd4e4272010-09-28 14:34:15 +020076 private PowerManager.WakeLock mCpuWakeLock;
77 private PowerManager.WakeLock mScreenWakeLock;
Dianne Hackborn55280a92009-05-07 15:53:46 -070078 private Handler mHandler;
79
80 private ShutdownThread() {
81 }
82
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080083 /**
Dianne Hackborn55280a92009-05-07 15:53:46 -070084 * 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.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080087 *
Dianne Hackborn55280a92009-05-07 15:53:46 -070088 * @param context Context used to display the shutdown progress dialog.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080089 * @param confirm true if user confirmation is needed before shutting down.
Dianne Hackborn55280a92009-05-07 15:53:46 -070090 */
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
Mike Lockwoodd67b2362010-07-26 07:18:21 -040094 synchronized (sIsStartedGuard) {
Dianne Hackborn55280a92009-05-07 15:53:46 -070095 if (sIsStarted) {
96 Log.d(TAG, "Request to shutdown already running, returning.");
97 return;
98 }
99 }
100
Joe Onoratod208e702010-10-08 16:22:43 -0400101 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);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700108
109 if (confirm) {
Joe Onoratod208e702010-10-08 16:22:43 -0400110 final CloseDialogReceiver closer = new CloseDialogReceiver(context);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700111 final AlertDialog dialog = new AlertDialog.Builder(context)
Dianne Hackborn55280a92009-05-07 15:53:46 -0700112 .setTitle(com.android.internal.R.string.power_off)
Joe Onoratod208e702010-10-08 16:22:43 -0400113 .setMessage(resourceId)
Dianne Hackborn55280a92009-05-07 15:53:46 -0700114 .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();
Joe Onoratod208e702010-10-08 16:22:43 -0400121 closer.dialog = dialog;
122 dialog.setOnDismissListener(closer);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700123 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700124 dialog.show();
125 } else {
126 beginShutdownSequence(context);
127 }
128 }
129
Joe Onoratod208e702010-10-08 16:22:43 -0400130 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
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800151 /**
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
Dianne Hackborn55280a92009-05-07 15:53:46 -0700166 private static void beginShutdownSequence(Context context) {
167 synchronized (sIsStartedGuard) {
Mike Lockwoodd67b2362010-07-26 07:18:21 -0400168 if (sIsStarted) {
Mathias Jeppsson8534a8e2010-08-17 13:33:09 +0200169 Log.d(TAG, "Shutdown sequence already running, returning.");
Mike Lockwoodd67b2362010-07-26 07:18:21 -0400170 return;
171 }
Dianne Hackborn55280a92009-05-07 15:53:46 -0700172 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);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700183
184 pd.show();
185
Dianne Hackborn55280a92009-05-07 15:53:46 -0700186 sInstance.mContext = context;
Dianne Hackbornf99ae762010-03-08 12:43:51 -0800187 sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
Mattias Larssoncd4e4272010-09-28 14:34:15 +0200188
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;
Dianne Hackbornf99ae762010-03-08 12:43:51 -0800203 if (sInstance.mPowerManager.isScreenOn()) {
204 try {
Mattias Larssoncd4e4272010-09-28 14:34:15 +0200205 sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
206 PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
207 sInstance.mScreenWakeLock.setReferenceCounted(false);
208 sInstance.mScreenWakeLock.acquire();
Dianne Hackbornf99ae762010-03-08 12:43:51 -0800209 } catch (SecurityException e) {
210 Log.w(TAG, "No permission to acquire wake lock", e);
Mattias Larssoncd4e4272010-09-28 14:34:15 +0200211 sInstance.mScreenWakeLock = null;
Dianne Hackbornf99ae762010-03-08 12:43:51 -0800212 }
213 }
Mattias Larssoncd4e4272010-09-28 14:34:15 +0200214
215 // start the thread that initiates shutdown
Dianne Hackborn55280a92009-05-07 15:53:46 -0700216 sInstance.mHandler = new Handler() {
217 };
218 sInstance.start();
219 }
220
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800221 void actionDone() {
222 synchronized (mActionDoneSync) {
223 mActionDone = true;
224 mActionDoneSync.notifyAll();
Dianne Hackborn55280a92009-05-07 15:53:46 -0700225 }
226 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800227
Dianne Hackborn55280a92009-05-07 15:53:46 -0700228 /**
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.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800239 actionDone();
Dianne Hackborn55280a92009-05-07 15:53:46 -0700240 }
241 };
Kenny Rootf547d672010-09-22 10:36:48 -0700242
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
Dianne Hackborn55280a92009-05-07 15:53:46 -0700253 Log.i(TAG, "Sending shutdown broadcast...");
254
255 // First send the high-level shut down broadcast.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800256 mActionDone = false;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700257 mContext.sendOrderedBroadcast(new Intent(Intent.ACTION_SHUTDOWN), null,
258 br, mHandler, 0, null, null);
259
Mike Lockwood098e58d2010-05-13 16:29:49 -0400260 final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800261 synchronized (mActionDoneSync) {
262 while (!mActionDone) {
Mike Lockwood098e58d2010-05-13 16:29:49 -0400263 long delay = endTime - SystemClock.elapsedRealtime();
Dianne Hackborn55280a92009-05-07 15:53:46 -0700264 if (delay <= 0) {
265 Log.w(TAG, "Shutdown broadcast timed out");
266 break;
267 }
268 try {
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800269 mActionDoneSync.wait(delay);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700270 } 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"));
Nick Pellybd022f42009-08-14 18:33:38 -0700288 final IBluetooth bluetooth =
289 IBluetooth.Stub.asInterface(ServiceManager.checkService(
Nick Pellyf242b7b2009-10-08 00:12:45 +0200290 BluetoothAdapter.BLUETOOTH_SERVICE));
San Mehat9f7f7ca2010-01-07 11:34:59 -0800291
292 final IMountService mount =
293 IMountService.Stub.asInterface(
294 ServiceManager.checkService("mount"));
Dianne Hackborn55280a92009-05-07 15:53:46 -0700295
296 try {
297 bluetoothOff = bluetooth == null ||
Nick Pellyde893f52009-09-08 13:15:33 -0700298 bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700299 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 =
Nick Pellyde893f52009-09-08 13:15:33 -0700326 bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700327 } 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
San Mehat9f7f7ca2010-01-07 11:34:59 -0800347 // Shutdown MountService to ensure media is in a safe state
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800348 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();
San Mehat9f7f7ca2010-01-07 11:34:59 -0800352 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800353 };
354
355 Log.i(TAG, "Shutting down MountService");
356 // Set initial variables and time out time.
357 mActionDone = false;
Mike Lockwood098e58d2010-05-13 16:29:49 -0400358 final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800359 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) {
Mike Lockwood098e58d2010-05-13 16:29:49 -0400370 long delay = endShutTime - SystemClock.elapsedRealtime();
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800371 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 }
San Mehat9f7f7ca2010-01-07 11:34:59 -0800380 }
381
Kenny Rootf547d672010-09-22 10:36:48 -0700382 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);
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800395 try {
Kenny Rootf547d672010-09-22 10:36:48 -0700396 Power.reboot(reason);
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800397 } catch (Exception e) {
398 Log.e(TAG, "Reboot failed, will attempt shutdown instead", e);
399 }
Mike Lockwooda717f642010-04-01 20:01:44 -0700400 } else if (SHUTDOWN_VIBRATE_MS > 0) {
401 // vibrate before shutting down
402 Vibrator vibrator = new Vibrator();
Brad Fitzpatrick26e9cf32010-10-19 09:33:09 -0700403 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
Mike Lockwooda717f642010-04-01 20:01:44 -0700410 // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
411 try {
412 Thread.sleep(SHUTDOWN_VIBRATE_MS);
Brad Fitzpatricke3316442010-10-14 19:40:56 -0700413 } catch (InterruptedException unused) {
Mike Lockwooda717f642010-04-01 20:01:44 -0700414 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800415 }
416
417 // Shutdown power
Dianne Hackborn55280a92009-05-07 15:53:46 -0700418 Log.i(TAG, "Performing low-level shutdown...");
419 Power.shutdown();
420 }
421}