blob: 8021f3c352e3c36afd5f7dfdd53274fa6e43b724 [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;
Sunil Jogi3bba8d02012-04-10 13:12:26 -070027import android.nfc.NfcAdapter;
28import android.nfc.INfcAdapter;
Dianne Hackborn55280a92009-05-07 15:53:46 -070029import android.content.BroadcastReceiver;
30import android.content.Context;
31import android.content.DialogInterface;
32import android.content.Intent;
Joe Onoratod208e702010-10-08 16:22:43 -040033import android.content.IntentFilter;
Dianne Hackborn55280a92009-05-07 15:53:46 -070034import android.os.Handler;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080035import android.os.Power;
Dianne Hackbornf99ae762010-03-08 12:43:51 -080036import android.os.PowerManager;
Dianne Hackborn55280a92009-05-07 15:53:46 -070037import android.os.RemoteException;
Dianne Hackborn55280a92009-05-07 15:53:46 -070038import android.os.ServiceManager;
39import android.os.SystemClock;
Kenny Rootf547d672010-09-22 10:36:48 -070040import android.os.SystemProperties;
Mike Lockwooda717f642010-04-01 20:01:44 -070041import android.os.Vibrator;
San Mehatb1043402010-02-05 08:26:50 -080042import android.os.storage.IMountService;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080043import android.os.storage.IMountShutdownObserver;
Dianne Hackborn568cae52009-10-07 16:13:39 -070044
Dianne Hackborn55280a92009-05-07 15:53:46 -070045import com.android.internal.telephony.ITelephony;
46import android.util.Log;
47import android.view.WindowManager;
48
49public final class ShutdownThread extends Thread {
50 // constants
51 private static final String TAG = "ShutdownThread";
Sunil Jogi3bba8d02012-04-10 13:12:26 -070052 private static final int MAX_NUM_PHONE_STATE_READS = 24;
Dianne Hackborn55280a92009-05-07 15:53:46 -070053 private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
54 // maximum time we wait for the shutdown broadcast before going on.
55 private static final int MAX_BROADCAST_TIME = 10*1000;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080056 private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
Mike Lockwooda717f642010-04-01 20:01:44 -070057
58 // length of vibration before shutting down
59 private static final int SHUTDOWN_VIBRATE_MS = 500;
Dianne Hackborn55280a92009-05-07 15:53:46 -070060
61 // state tracking
62 private static Object sIsStartedGuard = new Object();
63 private static boolean sIsStarted = false;
64
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080065 private static boolean mReboot;
66 private static String mRebootReason;
67
Kenny Rootf547d672010-09-22 10:36:48 -070068 // Provides shutdown assurance in case the system_server is killed
69 public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested";
70
Dianne Hackborn55280a92009-05-07 15:53:46 -070071 // static instance of this thread
72 private static final ShutdownThread sInstance = new ShutdownThread();
73
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080074 private final Object mActionDoneSync = new Object();
75 private boolean mActionDone;
Dianne Hackborn55280a92009-05-07 15:53:46 -070076 private Context mContext;
Dianne Hackbornf99ae762010-03-08 12:43:51 -080077 private PowerManager mPowerManager;
Mattias Larssoncd4e4272010-09-28 14:34:15 +020078 private PowerManager.WakeLock mCpuWakeLock;
79 private PowerManager.WakeLock mScreenWakeLock;
Dianne Hackborn55280a92009-05-07 15:53:46 -070080 private Handler mHandler;
81
82 private ShutdownThread() {
83 }
84
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080085 /**
Dianne Hackborn55280a92009-05-07 15:53:46 -070086 * Request a clean shutdown, waiting for subsystems to clean up their
87 * state etc. Must be called from a Looper thread in which its UI
88 * is shown.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080089 *
Dianne Hackborn55280a92009-05-07 15:53:46 -070090 * @param context Context used to display the shutdown progress dialog.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080091 * @param confirm true if user confirmation is needed before shutting down.
Dianne Hackborn55280a92009-05-07 15:53:46 -070092 */
93 public static void shutdown(final Context context, boolean confirm) {
94 // ensure that only one thread is trying to power down.
95 // any additional calls are just returned
Mike Lockwoodd67b2362010-07-26 07:18:21 -040096 synchronized (sIsStartedGuard) {
Dianne Hackborn55280a92009-05-07 15:53:46 -070097 if (sIsStarted) {
98 Log.d(TAG, "Request to shutdown already running, returning.");
99 return;
100 }
101 }
102
Joe Onoratod208e702010-10-08 16:22:43 -0400103 final int longPressBehavior = context.getResources().getInteger(
104 com.android.internal.R.integer.config_longPressOnPowerBehavior);
105 final int resourceId = longPressBehavior == 2
106 ? com.android.internal.R.string.shutdown_confirm_question
107 : com.android.internal.R.string.shutdown_confirm;
108
109 Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700110
111 if (confirm) {
Joe Onoratod208e702010-10-08 16:22:43 -0400112 final CloseDialogReceiver closer = new CloseDialogReceiver(context);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700113 final AlertDialog dialog = new AlertDialog.Builder(context)
Dianne Hackborn55280a92009-05-07 15:53:46 -0700114 .setTitle(com.android.internal.R.string.power_off)
Joe Onoratod208e702010-10-08 16:22:43 -0400115 .setMessage(resourceId)
Dianne Hackborn55280a92009-05-07 15:53:46 -0700116 .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
117 public void onClick(DialogInterface dialog, int which) {
118 beginShutdownSequence(context);
119 }
120 })
121 .setNegativeButton(com.android.internal.R.string.no, null)
122 .create();
Joe Onoratod208e702010-10-08 16:22:43 -0400123 closer.dialog = dialog;
124 dialog.setOnDismissListener(closer);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700125 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700126 dialog.show();
127 } else {
128 beginShutdownSequence(context);
129 }
130 }
131
Joe Onoratod208e702010-10-08 16:22:43 -0400132 private static class CloseDialogReceiver extends BroadcastReceiver
133 implements DialogInterface.OnDismissListener {
134 private Context mContext;
135 public Dialog dialog;
136
137 CloseDialogReceiver(Context context) {
138 mContext = context;
139 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
140 context.registerReceiver(this, filter);
141 }
142
143 @Override
144 public void onReceive(Context context, Intent intent) {
145 dialog.cancel();
146 }
147
148 public void onDismiss(DialogInterface unused) {
149 mContext.unregisterReceiver(this);
150 }
151 }
152
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800153 /**
154 * Request a clean shutdown, waiting for subsystems to clean up their
155 * state etc. Must be called from a Looper thread in which its UI
156 * is shown.
157 *
158 * @param context Context used to display the shutdown progress dialog.
159 * @param reason code to pass to the kernel (e.g. "recovery"), or null.
160 * @param confirm true if user confirmation is needed before shutting down.
161 */
162 public static void reboot(final Context context, String reason, boolean confirm) {
163 mReboot = true;
164 mRebootReason = reason;
165 shutdown(context, confirm);
166 }
167
Dianne Hackborn55280a92009-05-07 15:53:46 -0700168 private static void beginShutdownSequence(Context context) {
169 synchronized (sIsStartedGuard) {
Mike Lockwoodd67b2362010-07-26 07:18:21 -0400170 if (sIsStarted) {
Mathias Jeppsson8534a8e2010-08-17 13:33:09 +0200171 Log.d(TAG, "Shutdown sequence already running, returning.");
Mike Lockwoodd67b2362010-07-26 07:18:21 -0400172 return;
173 }
Dianne Hackborn55280a92009-05-07 15:53:46 -0700174 sIsStarted = true;
175 }
176
177 // throw up an indeterminate system dialog to indicate radio is
178 // shutting down.
179 ProgressDialog pd = new ProgressDialog(context);
180 pd.setTitle(context.getText(com.android.internal.R.string.power_off));
181 pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
182 pd.setIndeterminate(true);
183 pd.setCancelable(false);
184 pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700185
186 pd.show();
187
Dianne Hackborn55280a92009-05-07 15:53:46 -0700188 sInstance.mContext = context;
Dianne Hackbornf99ae762010-03-08 12:43:51 -0800189 sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
Mattias Larssoncd4e4272010-09-28 14:34:15 +0200190
191 // make sure we never fall asleep again
192 sInstance.mCpuWakeLock = null;
193 try {
194 sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
195 PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
196 sInstance.mCpuWakeLock.setReferenceCounted(false);
197 sInstance.mCpuWakeLock.acquire();
198 } catch (SecurityException e) {
199 Log.w(TAG, "No permission to acquire wake lock", e);
200 sInstance.mCpuWakeLock = null;
201 }
202
203 // also make sure the screen stays on for better user experience
204 sInstance.mScreenWakeLock = null;
Dianne Hackbornf99ae762010-03-08 12:43:51 -0800205 if (sInstance.mPowerManager.isScreenOn()) {
206 try {
Mattias Larssoncd4e4272010-09-28 14:34:15 +0200207 sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
208 PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
209 sInstance.mScreenWakeLock.setReferenceCounted(false);
210 sInstance.mScreenWakeLock.acquire();
Dianne Hackbornf99ae762010-03-08 12:43:51 -0800211 } catch (SecurityException e) {
212 Log.w(TAG, "No permission to acquire wake lock", e);
Mattias Larssoncd4e4272010-09-28 14:34:15 +0200213 sInstance.mScreenWakeLock = null;
Dianne Hackbornf99ae762010-03-08 12:43:51 -0800214 }
215 }
Mattias Larssoncd4e4272010-09-28 14:34:15 +0200216
217 // start the thread that initiates shutdown
Dianne Hackborn55280a92009-05-07 15:53:46 -0700218 sInstance.mHandler = new Handler() {
219 };
220 sInstance.start();
221 }
222
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800223 void actionDone() {
224 synchronized (mActionDoneSync) {
225 mActionDone = true;
226 mActionDoneSync.notifyAll();
Dianne Hackborn55280a92009-05-07 15:53:46 -0700227 }
228 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800229
Dianne Hackborn55280a92009-05-07 15:53:46 -0700230 /**
231 * Makes sure we handle the shutdown gracefully.
232 * Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
233 */
234 public void run() {
Sunil Jogi3bba8d02012-04-10 13:12:26 -0700235 boolean nfcOff;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700236 boolean bluetoothOff;
237 boolean radioOff;
238
239 BroadcastReceiver br = new BroadcastReceiver() {
240 @Override public void onReceive(Context context, Intent intent) {
241 // We don't allow apps to cancel this, so ignore the result.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800242 actionDone();
Dianne Hackborn55280a92009-05-07 15:53:46 -0700243 }
244 };
Kenny Rootf547d672010-09-22 10:36:48 -0700245
246 /*
247 * Write a system property in case the system_server reboots before we
248 * get to the actual hardware restart. If that happens, we'll retry at
249 * the beginning of the SystemServer startup.
250 */
251 {
252 String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : "");
253 SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
254 }
255
Dianne Hackborn55280a92009-05-07 15:53:46 -0700256 Log.i(TAG, "Sending shutdown broadcast...");
257
258 // First send the high-level shut down broadcast.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800259 mActionDone = false;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700260 mContext.sendOrderedBroadcast(new Intent(Intent.ACTION_SHUTDOWN), null,
261 br, mHandler, 0, null, null);
262
Mike Lockwood098e58d2010-05-13 16:29:49 -0400263 final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800264 synchronized (mActionDoneSync) {
265 while (!mActionDone) {
Mike Lockwood098e58d2010-05-13 16:29:49 -0400266 long delay = endTime - SystemClock.elapsedRealtime();
Dianne Hackborn55280a92009-05-07 15:53:46 -0700267 if (delay <= 0) {
268 Log.w(TAG, "Shutdown broadcast timed out");
269 break;
270 }
271 try {
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800272 mActionDoneSync.wait(delay);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700273 } catch (InterruptedException e) {
274 }
275 }
276 }
277
278 Log.i(TAG, "Shutting down activity manager...");
279
280 final IActivityManager am =
281 ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
282 if (am != null) {
283 try {
284 am.shutdown(MAX_BROADCAST_TIME);
285 } catch (RemoteException e) {
286 }
287 }
288
Sunil Jogi3bba8d02012-04-10 13:12:26 -0700289 final INfcAdapter nfc =
290 INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc"));
Dianne Hackborn55280a92009-05-07 15:53:46 -0700291 final ITelephony phone =
292 ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
Nick Pellybd022f42009-08-14 18:33:38 -0700293 final IBluetooth bluetooth =
294 IBluetooth.Stub.asInterface(ServiceManager.checkService(
Nick Pellyf242b7b2009-10-08 00:12:45 +0200295 BluetoothAdapter.BLUETOOTH_SERVICE));
San Mehat9f7f7ca2010-01-07 11:34:59 -0800296 final IMountService mount =
297 IMountService.Stub.asInterface(
298 ServiceManager.checkService("mount"));
Sunil Jogi3bba8d02012-04-10 13:12:26 -0700299
300 try {
301 nfcOff = nfc == null ||
302 nfc.getState() == NfcAdapter.STATE_OFF;
303 if (!nfcOff) {
304 Log.w(TAG, "Turning off NFC...");
305 nfc.disable(false); // Don't persist new state
306 }
307 } catch (RemoteException ex) {
308 Log.e(TAG, "RemoteException during NFC shutdown", ex);
309 nfcOff = true;
310 }
311
Dianne Hackborn55280a92009-05-07 15:53:46 -0700312 try {
313 bluetoothOff = bluetooth == null ||
Nick Pellyde893f52009-09-08 13:15:33 -0700314 bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700315 if (!bluetoothOff) {
316 Log.w(TAG, "Disabling Bluetooth...");
317 bluetooth.disable(false); // disable but don't persist new state
318 }
319 } catch (RemoteException ex) {
320 Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
321 bluetoothOff = true;
322 }
323
324 try {
325 radioOff = phone == null || !phone.isRadioOn();
326 if (!radioOff) {
327 Log.w(TAG, "Turning off radio...");
328 phone.setRadio(false);
329 }
330 } catch (RemoteException ex) {
331 Log.e(TAG, "RemoteException during radio shutdown", ex);
332 radioOff = true;
333 }
334
Sunil Jogi3bba8d02012-04-10 13:12:26 -0700335 Log.i(TAG, "Waiting for NFC, Bluetooth and Radio...");
Dianne Hackborn55280a92009-05-07 15:53:46 -0700336
337 // Wait a max of 32 seconds for clean shutdown
338 for (int i = 0; i < MAX_NUM_PHONE_STATE_READS; i++) {
339 if (!bluetoothOff) {
340 try {
341 bluetoothOff =
Nick Pellyde893f52009-09-08 13:15:33 -0700342 bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700343 } catch (RemoteException ex) {
344 Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
345 bluetoothOff = true;
346 }
347 }
348 if (!radioOff) {
349 try {
350 radioOff = !phone.isRadioOn();
351 } catch (RemoteException ex) {
352 Log.e(TAG, "RemoteException during radio shutdown", ex);
353 radioOff = true;
354 }
355 }
Sunil Jogi3bba8d02012-04-10 13:12:26 -0700356 if (!nfcOff) {
357 try {
358 nfcOff = nfc.getState() == NfcAdapter.STATE_OFF;
359 } catch (RemoteException ex) {
360 Log.e(TAG, "RemoteException during NFC shutdown", ex);
361 nfcOff = true;
362 }
363 }
364
365 if (radioOff && bluetoothOff && nfcOff) {
366 Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete.");
Dianne Hackborn55280a92009-05-07 15:53:46 -0700367 break;
368 }
369 SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
370 }
371
San Mehat9f7f7ca2010-01-07 11:34:59 -0800372 // Shutdown MountService to ensure media is in a safe state
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800373 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();
San Mehat9f7f7ca2010-01-07 11:34:59 -0800377 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800378 };
379
380 Log.i(TAG, "Shutting down MountService");
381 // Set initial variables and time out time.
382 mActionDone = false;
Mike Lockwood098e58d2010-05-13 16:29:49 -0400383 final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800384 synchronized (mActionDoneSync) {
385 try {
386 if (mount != null) {
387 mount.shutdown(observer);
388 } else {
389 Log.w(TAG, "MountService unavailable for shutdown");
390 }
391 } catch (Exception e) {
392 Log.e(TAG, "Exception during MountService shutdown", e);
393 }
394 while (!mActionDone) {
Mike Lockwood098e58d2010-05-13 16:29:49 -0400395 long delay = endShutTime - SystemClock.elapsedRealtime();
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800396 if (delay <= 0) {
397 Log.w(TAG, "Shutdown wait timed out");
398 break;
399 }
400 try {
401 mActionDoneSync.wait(delay);
402 } catch (InterruptedException e) {
403 }
404 }
San Mehat9f7f7ca2010-01-07 11:34:59 -0800405 }
406
Kenny Rootf547d672010-09-22 10:36:48 -0700407 rebootOrShutdown(mReboot, mRebootReason);
408 }
409
410 /**
411 * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
412 * or {@link #shutdown(Context, boolean)} instead.
413 *
414 * @param reboot true to reboot or false to shutdown
415 * @param reason reason for reboot
416 */
417 public static void rebootOrShutdown(boolean reboot, String reason) {
418 if (reboot) {
419 Log.i(TAG, "Rebooting, reason: " + reason);
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800420 try {
Kenny Rootf547d672010-09-22 10:36:48 -0700421 Power.reboot(reason);
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800422 } catch (Exception e) {
423 Log.e(TAG, "Reboot failed, will attempt shutdown instead", e);
424 }
Mike Lockwooda717f642010-04-01 20:01:44 -0700425 } else if (SHUTDOWN_VIBRATE_MS > 0) {
426 // vibrate before shutting down
427 Vibrator vibrator = new Vibrator();
Brad Fitzpatrick26e9cf32010-10-19 09:33:09 -0700428 try {
429 vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
430 } catch (Exception e) {
431 // Failure to vibrate shouldn't interrupt shutdown. Just log it.
432 Log.w(TAG, "Failed to vibrate during shutdown.", e);
433 }
434
Mike Lockwooda717f642010-04-01 20:01:44 -0700435 // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
436 try {
437 Thread.sleep(SHUTDOWN_VIBRATE_MS);
Brad Fitzpatricke3316442010-10-14 19:40:56 -0700438 } catch (InterruptedException unused) {
Mike Lockwooda717f642010-04-01 20:01:44 -0700439 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800440 }
441
442 // Shutdown power
Dianne Hackborn55280a92009-05-07 15:53:46 -0700443 Log.i(TAG, "Performing low-level shutdown...");
444 Power.shutdown();
445 }
446}