blob: 6a469293ca52708a24c5b80ba9a45f989bd61279 [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;
Jeff Brownc2346132012-04-13 01:55:38 -070040import android.os.SystemVibrator;
San Mehatb1043402010-02-05 08:26:50 -080041import android.os.storage.IMountService;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080042import android.os.storage.IMountShutdownObserver;
Dianne Hackborn568cae52009-10-07 16:13:39 -070043
Dianne Hackborn55280a92009-05-07 15:53:46 -070044import com.android.internal.telephony.ITelephony;
45import android.util.Log;
46import android.view.WindowManager;
47
48public final class ShutdownThread extends Thread {
49 // constants
50 private static final String TAG = "ShutdownThread";
51 private static final int MAX_NUM_PHONE_STATE_READS = 16;
52 private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
53 // maximum time we wait for the shutdown broadcast before going on.
54 private static final int MAX_BROADCAST_TIME = 10*1000;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080055 private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
Mike Lockwooda717f642010-04-01 20:01:44 -070056
57 // length of vibration before shutting down
58 private static final int SHUTDOWN_VIBRATE_MS = 500;
Dianne Hackborn55280a92009-05-07 15:53:46 -070059
60 // state tracking
61 private static Object sIsStartedGuard = new Object();
62 private static boolean sIsStarted = false;
63
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080064 private static boolean mReboot;
65 private static String mRebootReason;
66
Kenny Rootf547d672010-09-22 10:36:48 -070067 // Provides shutdown assurance in case the system_server is killed
68 public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested";
69
Dianne Hackborn55280a92009-05-07 15:53:46 -070070 // static instance of this thread
71 private static final ShutdownThread sInstance = new ShutdownThread();
72
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080073 private final Object mActionDoneSync = new Object();
74 private boolean mActionDone;
Dianne Hackborn55280a92009-05-07 15:53:46 -070075 private Context mContext;
Dianne Hackbornf99ae762010-03-08 12:43:51 -080076 private PowerManager mPowerManager;
Mattias Larssoncd4e4272010-09-28 14:34:15 +020077 private PowerManager.WakeLock mCpuWakeLock;
78 private PowerManager.WakeLock mScreenWakeLock;
Dianne Hackborn55280a92009-05-07 15:53:46 -070079 private Handler mHandler;
80
81 private ShutdownThread() {
82 }
83
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080084 /**
Dianne Hackborn55280a92009-05-07 15:53:46 -070085 * Request a clean shutdown, waiting for subsystems to clean up their
86 * state etc. Must be called from a Looper thread in which its UI
87 * is shown.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080088 *
Dianne Hackborn55280a92009-05-07 15:53:46 -070089 * @param context Context used to display the shutdown progress dialog.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080090 * @param confirm true if user confirmation is needed before shutting down.
Dianne Hackborn55280a92009-05-07 15:53:46 -070091 */
92 public static void shutdown(final Context context, boolean confirm) {
93 // ensure that only one thread is trying to power down.
94 // any additional calls are just returned
Mike Lockwoodd67b2362010-07-26 07:18:21 -040095 synchronized (sIsStartedGuard) {
Dianne Hackborn55280a92009-05-07 15:53:46 -070096 if (sIsStarted) {
97 Log.d(TAG, "Request to shutdown already running, returning.");
98 return;
99 }
100 }
101
Joe Onoratod208e702010-10-08 16:22:43 -0400102 final int longPressBehavior = context.getResources().getInteger(
103 com.android.internal.R.integer.config_longPressOnPowerBehavior);
104 final int resourceId = longPressBehavior == 2
105 ? com.android.internal.R.string.shutdown_confirm_question
106 : com.android.internal.R.string.shutdown_confirm;
107
108 Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700109
110 if (confirm) {
Joe Onoratod208e702010-10-08 16:22:43 -0400111 final CloseDialogReceiver closer = new CloseDialogReceiver(context);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700112 final AlertDialog dialog = new AlertDialog.Builder(context)
Dianne Hackborn55280a92009-05-07 15:53:46 -0700113 .setTitle(com.android.internal.R.string.power_off)
Joe Onoratod208e702010-10-08 16:22:43 -0400114 .setMessage(resourceId)
Dianne Hackborn55280a92009-05-07 15:53:46 -0700115 .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
116 public void onClick(DialogInterface dialog, int which) {
117 beginShutdownSequence(context);
118 }
119 })
120 .setNegativeButton(com.android.internal.R.string.no, null)
121 .create();
Joe Onoratod208e702010-10-08 16:22:43 -0400122 closer.dialog = dialog;
123 dialog.setOnDismissListener(closer);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700124 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700125 dialog.show();
126 } else {
127 beginShutdownSequence(context);
128 }
129 }
130
Joe Onoratod208e702010-10-08 16:22:43 -0400131 private static class CloseDialogReceiver extends BroadcastReceiver
132 implements DialogInterface.OnDismissListener {
133 private Context mContext;
134 public Dialog dialog;
135
136 CloseDialogReceiver(Context context) {
137 mContext = context;
138 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
139 context.registerReceiver(this, filter);
140 }
141
142 @Override
143 public void onReceive(Context context, Intent intent) {
144 dialog.cancel();
145 }
146
147 public void onDismiss(DialogInterface unused) {
148 mContext.unregisterReceiver(this);
149 }
150 }
151
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800152 /**
153 * Request a clean shutdown, waiting for subsystems to clean up their
154 * state etc. Must be called from a Looper thread in which its UI
155 * is shown.
156 *
157 * @param context Context used to display the shutdown progress dialog.
158 * @param reason code to pass to the kernel (e.g. "recovery"), or null.
159 * @param confirm true if user confirmation is needed before shutting down.
160 */
161 public static void reboot(final Context context, String reason, boolean confirm) {
162 mReboot = true;
163 mRebootReason = reason;
164 shutdown(context, confirm);
165 }
166
Dianne Hackborn55280a92009-05-07 15:53:46 -0700167 private static void beginShutdownSequence(Context context) {
168 synchronized (sIsStartedGuard) {
Mike Lockwoodd67b2362010-07-26 07:18:21 -0400169 if (sIsStarted) {
Mathias Jeppsson8534a8e2010-08-17 13:33:09 +0200170 Log.d(TAG, "Shutdown sequence already running, returning.");
Mike Lockwoodd67b2362010-07-26 07:18:21 -0400171 return;
172 }
Dianne Hackborn55280a92009-05-07 15:53:46 -0700173 sIsStarted = true;
174 }
175
176 // throw up an indeterminate system dialog to indicate radio is
177 // shutting down.
178 ProgressDialog pd = new ProgressDialog(context);
179 pd.setTitle(context.getText(com.android.internal.R.string.power_off));
180 pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
181 pd.setIndeterminate(true);
182 pd.setCancelable(false);
183 pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700184
185 pd.show();
186
Dianne Hackborn55280a92009-05-07 15:53:46 -0700187 sInstance.mContext = context;
Dianne Hackbornf99ae762010-03-08 12:43:51 -0800188 sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
Mattias Larssoncd4e4272010-09-28 14:34:15 +0200189
190 // make sure we never fall asleep again
191 sInstance.mCpuWakeLock = null;
192 try {
193 sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
194 PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
195 sInstance.mCpuWakeLock.setReferenceCounted(false);
196 sInstance.mCpuWakeLock.acquire();
197 } catch (SecurityException e) {
198 Log.w(TAG, "No permission to acquire wake lock", e);
199 sInstance.mCpuWakeLock = null;
200 }
201
202 // also make sure the screen stays on for better user experience
203 sInstance.mScreenWakeLock = null;
Dianne Hackbornf99ae762010-03-08 12:43:51 -0800204 if (sInstance.mPowerManager.isScreenOn()) {
205 try {
Mattias Larssoncd4e4272010-09-28 14:34:15 +0200206 sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
207 PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
208 sInstance.mScreenWakeLock.setReferenceCounted(false);
209 sInstance.mScreenWakeLock.acquire();
Dianne Hackbornf99ae762010-03-08 12:43:51 -0800210 } catch (SecurityException e) {
211 Log.w(TAG, "No permission to acquire wake lock", e);
Mattias Larssoncd4e4272010-09-28 14:34:15 +0200212 sInstance.mScreenWakeLock = null;
Dianne Hackbornf99ae762010-03-08 12:43:51 -0800213 }
214 }
Mattias Larssoncd4e4272010-09-28 14:34:15 +0200215
216 // start the thread that initiates shutdown
Dianne Hackborn55280a92009-05-07 15:53:46 -0700217 sInstance.mHandler = new Handler() {
218 };
219 sInstance.start();
220 }
221
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800222 void actionDone() {
223 synchronized (mActionDoneSync) {
224 mActionDone = true;
225 mActionDoneSync.notifyAll();
Dianne Hackborn55280a92009-05-07 15:53:46 -0700226 }
227 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800228
Dianne Hackborn55280a92009-05-07 15:53:46 -0700229 /**
230 * Makes sure we handle the shutdown gracefully.
231 * Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
232 */
233 public void run() {
234 boolean bluetoothOff;
235 boolean radioOff;
236
237 BroadcastReceiver br = new BroadcastReceiver() {
238 @Override public void onReceive(Context context, Intent intent) {
239 // We don't allow apps to cancel this, so ignore the result.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800240 actionDone();
Dianne Hackborn55280a92009-05-07 15:53:46 -0700241 }
242 };
Kenny Rootf547d672010-09-22 10:36:48 -0700243
244 /*
245 * Write a system property in case the system_server reboots before we
246 * get to the actual hardware restart. If that happens, we'll retry at
247 * the beginning of the SystemServer startup.
248 */
249 {
250 String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : "");
251 SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
252 }
253
Dianne Hackborn55280a92009-05-07 15:53:46 -0700254 Log.i(TAG, "Sending shutdown broadcast...");
255
256 // First send the high-level shut down broadcast.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800257 mActionDone = false;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700258 mContext.sendOrderedBroadcast(new Intent(Intent.ACTION_SHUTDOWN), null,
259 br, mHandler, 0, null, null);
260
Mike Lockwood098e58d2010-05-13 16:29:49 -0400261 final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800262 synchronized (mActionDoneSync) {
263 while (!mActionDone) {
Mike Lockwood098e58d2010-05-13 16:29:49 -0400264 long delay = endTime - SystemClock.elapsedRealtime();
Dianne Hackborn55280a92009-05-07 15:53:46 -0700265 if (delay <= 0) {
266 Log.w(TAG, "Shutdown broadcast timed out");
267 break;
268 }
269 try {
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800270 mActionDoneSync.wait(delay);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700271 } catch (InterruptedException e) {
272 }
273 }
274 }
275
276 Log.i(TAG, "Shutting down activity manager...");
277
278 final IActivityManager am =
279 ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
280 if (am != null) {
281 try {
282 am.shutdown(MAX_BROADCAST_TIME);
283 } catch (RemoteException e) {
284 }
285 }
286
287 final ITelephony phone =
288 ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
Nick Pellybd022f42009-08-14 18:33:38 -0700289 final IBluetooth bluetooth =
290 IBluetooth.Stub.asInterface(ServiceManager.checkService(
Nick Pellyf242b7b2009-10-08 00:12:45 +0200291 BluetoothAdapter.BLUETOOTH_SERVICE));
San Mehat9f7f7ca2010-01-07 11:34:59 -0800292
293 final IMountService mount =
294 IMountService.Stub.asInterface(
295 ServiceManager.checkService("mount"));
Dianne Hackborn55280a92009-05-07 15:53:46 -0700296
297 try {
298 bluetoothOff = bluetooth == null ||
Nick Pellyde893f52009-09-08 13:15:33 -0700299 bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700300 if (!bluetoothOff) {
301 Log.w(TAG, "Disabling Bluetooth...");
302 bluetooth.disable(false); // disable but don't persist new state
303 }
304 } catch (RemoteException ex) {
305 Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
306 bluetoothOff = true;
307 }
308
309 try {
310 radioOff = phone == null || !phone.isRadioOn();
311 if (!radioOff) {
312 Log.w(TAG, "Turning off radio...");
313 phone.setRadio(false);
314 }
315 } catch (RemoteException ex) {
316 Log.e(TAG, "RemoteException during radio shutdown", ex);
317 radioOff = true;
318 }
319
320 Log.i(TAG, "Waiting for Bluetooth and Radio...");
321
322 // Wait a max of 32 seconds for clean shutdown
323 for (int i = 0; i < MAX_NUM_PHONE_STATE_READS; i++) {
324 if (!bluetoothOff) {
325 try {
326 bluetoothOff =
Nick Pellyde893f52009-09-08 13:15:33 -0700327 bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700328 } catch (RemoteException ex) {
329 Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
330 bluetoothOff = true;
331 }
332 }
333 if (!radioOff) {
334 try {
335 radioOff = !phone.isRadioOn();
336 } catch (RemoteException ex) {
337 Log.e(TAG, "RemoteException during radio shutdown", ex);
338 radioOff = true;
339 }
340 }
341 if (radioOff && bluetoothOff) {
342 Log.i(TAG, "Radio and Bluetooth shutdown complete.");
343 break;
344 }
345 SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
346 }
347
San Mehat9f7f7ca2010-01-07 11:34:59 -0800348 // Shutdown MountService to ensure media is in a safe state
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800349 IMountShutdownObserver observer = new IMountShutdownObserver.Stub() {
350 public void onShutDownComplete(int statusCode) throws RemoteException {
351 Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown");
352 actionDone();
San Mehat9f7f7ca2010-01-07 11:34:59 -0800353 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800354 };
355
356 Log.i(TAG, "Shutting down MountService");
357 // Set initial variables and time out time.
358 mActionDone = false;
Mike Lockwood098e58d2010-05-13 16:29:49 -0400359 final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800360 synchronized (mActionDoneSync) {
361 try {
362 if (mount != null) {
363 mount.shutdown(observer);
364 } else {
365 Log.w(TAG, "MountService unavailable for shutdown");
366 }
367 } catch (Exception e) {
368 Log.e(TAG, "Exception during MountService shutdown", e);
369 }
370 while (!mActionDone) {
Mike Lockwood098e58d2010-05-13 16:29:49 -0400371 long delay = endShutTime - SystemClock.elapsedRealtime();
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800372 if (delay <= 0) {
373 Log.w(TAG, "Shutdown wait timed out");
374 break;
375 }
376 try {
377 mActionDoneSync.wait(delay);
378 } catch (InterruptedException e) {
379 }
380 }
San Mehat9f7f7ca2010-01-07 11:34:59 -0800381 }
382
Kenny Rootf547d672010-09-22 10:36:48 -0700383 rebootOrShutdown(mReboot, mRebootReason);
384 }
385
386 /**
387 * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
388 * or {@link #shutdown(Context, boolean)} instead.
389 *
390 * @param reboot true to reboot or false to shutdown
391 * @param reason reason for reboot
392 */
393 public static void rebootOrShutdown(boolean reboot, String reason) {
394 if (reboot) {
395 Log.i(TAG, "Rebooting, reason: " + reason);
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800396 try {
Kenny Rootf547d672010-09-22 10:36:48 -0700397 Power.reboot(reason);
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800398 } catch (Exception e) {
399 Log.e(TAG, "Reboot failed, will attempt shutdown instead", e);
400 }
Mike Lockwooda717f642010-04-01 20:01:44 -0700401 } else if (SHUTDOWN_VIBRATE_MS > 0) {
402 // vibrate before shutting down
Jeff Brownc2346132012-04-13 01:55:38 -0700403 Vibrator vibrator = new SystemVibrator();
Brad Fitzpatrick26e9cf32010-10-19 09:33:09 -0700404 try {
405 vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
406 } catch (Exception e) {
407 // Failure to vibrate shouldn't interrupt shutdown. Just log it.
408 Log.w(TAG, "Failed to vibrate during shutdown.", e);
409 }
410
Mike Lockwooda717f642010-04-01 20:01:44 -0700411 // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
412 try {
413 Thread.sleep(SHUTDOWN_VIBRATE_MS);
Brad Fitzpatricke3316442010-10-14 19:40:56 -0700414 } catch (InterruptedException unused) {
Mike Lockwooda717f642010-04-01 20:01:44 -0700415 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800416 }
417
418 // Shutdown power
Dianne Hackborn55280a92009-05-07 15:53:46 -0700419 Log.i(TAG, "Performing low-level shutdown...");
420 Power.shutdown();
421 }
422}