blob: e35edc3400b029d6d88f5f99208f4b2041d5ef21 [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;
21import android.app.IActivityManager;
22import android.app.ProgressDialog;
23import android.app.AlertDialog;
Nick Pellybd022f42009-08-14 18:33:38 -070024import android.bluetooth.BluetoothAdapter;
25import android.bluetooth.IBluetooth;
Dianne Hackborn55280a92009-05-07 15:53:46 -070026import android.content.BroadcastReceiver;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.Intent;
30import android.os.Handler;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080031import android.os.Power;
Dianne Hackbornf99ae762010-03-08 12:43:51 -080032import android.os.PowerManager;
Dianne Hackborn55280a92009-05-07 15:53:46 -070033import android.os.RemoteException;
Dianne Hackborn55280a92009-05-07 15:53:46 -070034import android.os.ServiceManager;
35import android.os.SystemClock;
Mike Lockwooda717f642010-04-01 20:01:44 -070036import android.os.Vibrator;
San Mehatb1043402010-02-05 08:26:50 -080037import android.os.storage.IMountService;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080038import android.os.storage.IMountShutdownObserver;
Dianne Hackborn568cae52009-10-07 16:13:39 -070039
Dianne Hackborn55280a92009-05-07 15:53:46 -070040import com.android.internal.telephony.ITelephony;
41import android.util.Log;
42import android.view.WindowManager;
43
44public final class ShutdownThread extends Thread {
45 // constants
46 private static final String TAG = "ShutdownThread";
47 private static final int MAX_NUM_PHONE_STATE_READS = 16;
48 private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
49 // maximum time we wait for the shutdown broadcast before going on.
50 private static final int MAX_BROADCAST_TIME = 10*1000;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080051 private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
Mike Lockwooda717f642010-04-01 20:01:44 -070052
53 // length of vibration before shutting down
54 private static final int SHUTDOWN_VIBRATE_MS = 500;
Dianne Hackborn55280a92009-05-07 15:53:46 -070055
56 // state tracking
57 private static Object sIsStartedGuard = new Object();
58 private static boolean sIsStarted = false;
59
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080060 private static boolean mReboot;
61 private static String mRebootReason;
62
Dianne Hackborn55280a92009-05-07 15:53:46 -070063 // static instance of this thread
64 private static final ShutdownThread sInstance = new ShutdownThread();
65
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080066 private final Object mActionDoneSync = new Object();
67 private boolean mActionDone;
Dianne Hackborn55280a92009-05-07 15:53:46 -070068 private Context mContext;
Dianne Hackbornf99ae762010-03-08 12:43:51 -080069 private PowerManager mPowerManager;
70 private PowerManager.WakeLock mWakeLock;
Dianne Hackborn55280a92009-05-07 15:53:46 -070071 private Handler mHandler;
72
73 private ShutdownThread() {
74 }
75
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080076 /**
Dianne Hackborn55280a92009-05-07 15:53:46 -070077 * Request a clean shutdown, waiting for subsystems to clean up their
78 * state etc. Must be called from a Looper thread in which its UI
79 * is shown.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080080 *
Dianne Hackborn55280a92009-05-07 15:53:46 -070081 * @param context Context used to display the shutdown progress dialog.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080082 * @param confirm true if user confirmation is needed before shutting down.
Dianne Hackborn55280a92009-05-07 15:53:46 -070083 */
84 public static void shutdown(final Context context, boolean confirm) {
85 // ensure that only one thread is trying to power down.
86 // any additional calls are just returned
87 synchronized (sIsStartedGuard){
88 if (sIsStarted) {
89 Log.d(TAG, "Request to shutdown already running, returning.");
90 return;
91 }
92 }
93
94 Log.d(TAG, "Notifying thread to start radio shutdown");
95
96 if (confirm) {
97 final AlertDialog dialog = new AlertDialog.Builder(context)
98 .setIcon(android.R.drawable.ic_dialog_alert)
99 .setTitle(com.android.internal.R.string.power_off)
100 .setMessage(com.android.internal.R.string.shutdown_confirm)
101 .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
102 public void onClick(DialogInterface dialog, int which) {
103 beginShutdownSequence(context);
104 }
105 })
106 .setNegativeButton(com.android.internal.R.string.no, null)
107 .create();
108 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
Dianne Hackborn568cae52009-10-07 16:13:39 -0700109 if (!context.getResources().getBoolean(
110 com.android.internal.R.bool.config_sf_slowBlur)) {
111 dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
112 }
Dianne Hackborn55280a92009-05-07 15:53:46 -0700113 dialog.show();
114 } else {
115 beginShutdownSequence(context);
116 }
117 }
118
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800119 /**
120 * Request a clean shutdown, waiting for subsystems to clean up their
121 * state etc. Must be called from a Looper thread in which its UI
122 * is shown.
123 *
124 * @param context Context used to display the shutdown progress dialog.
125 * @param reason code to pass to the kernel (e.g. "recovery"), or null.
126 * @param confirm true if user confirmation is needed before shutting down.
127 */
128 public static void reboot(final Context context, String reason, boolean confirm) {
129 mReboot = true;
130 mRebootReason = reason;
131 shutdown(context, confirm);
132 }
133
Dianne Hackborn55280a92009-05-07 15:53:46 -0700134 private static void beginShutdownSequence(Context context) {
135 synchronized (sIsStartedGuard) {
Mathias Jeppsson8534a8e2010-08-17 13:33:09 +0200136 if (sIsStarted) {
137 Log.d(TAG, "Shutdown sequence already running, returning.");
138 return;
139 }
Dianne Hackborn55280a92009-05-07 15:53:46 -0700140 sIsStarted = true;
141 }
142
143 // throw up an indeterminate system dialog to indicate radio is
144 // shutting down.
145 ProgressDialog pd = new ProgressDialog(context);
146 pd.setTitle(context.getText(com.android.internal.R.string.power_off));
147 pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
148 pd.setIndeterminate(true);
149 pd.setCancelable(false);
150 pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
Dianne Hackborn568cae52009-10-07 16:13:39 -0700151 if (!context.getResources().getBoolean(
152 com.android.internal.R.bool.config_sf_slowBlur)) {
153 pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
154 }
Dianne Hackborn55280a92009-05-07 15:53:46 -0700155
156 pd.show();
157
158 // start the thread that initiates shutdown
159 sInstance.mContext = context;
Dianne Hackbornf99ae762010-03-08 12:43:51 -0800160 sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
161 sInstance.mWakeLock = null;
162 if (sInstance.mPowerManager.isScreenOn()) {
163 try {
164 sInstance.mWakeLock = sInstance.mPowerManager.newWakeLock(
165 PowerManager.FULL_WAKE_LOCK, "Shutdown");
166 sInstance.mWakeLock.acquire();
167 } catch (SecurityException e) {
168 Log.w(TAG, "No permission to acquire wake lock", e);
169 sInstance.mWakeLock = null;
170 }
171 }
Dianne Hackborn55280a92009-05-07 15:53:46 -0700172 sInstance.mHandler = new Handler() {
173 };
174 sInstance.start();
175 }
176
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800177 void actionDone() {
178 synchronized (mActionDoneSync) {
179 mActionDone = true;
180 mActionDoneSync.notifyAll();
Dianne Hackborn55280a92009-05-07 15:53:46 -0700181 }
182 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800183
Dianne Hackborn55280a92009-05-07 15:53:46 -0700184 /**
185 * Makes sure we handle the shutdown gracefully.
186 * Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
187 */
188 public void run() {
189 boolean bluetoothOff;
190 boolean radioOff;
191
192 BroadcastReceiver br = new BroadcastReceiver() {
193 @Override public void onReceive(Context context, Intent intent) {
194 // We don't allow apps to cancel this, so ignore the result.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800195 actionDone();
Dianne Hackborn55280a92009-05-07 15:53:46 -0700196 }
197 };
198
199 Log.i(TAG, "Sending shutdown broadcast...");
200
201 // First send the high-level shut down broadcast.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800202 mActionDone = false;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700203 mContext.sendOrderedBroadcast(new Intent(Intent.ACTION_SHUTDOWN), null,
204 br, mHandler, 0, null, null);
205
Mike Lockwood098e58d2010-05-13 16:29:49 -0400206 final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800207 synchronized (mActionDoneSync) {
208 while (!mActionDone) {
Mike Lockwood098e58d2010-05-13 16:29:49 -0400209 long delay = endTime - SystemClock.elapsedRealtime();
Dianne Hackborn55280a92009-05-07 15:53:46 -0700210 if (delay <= 0) {
211 Log.w(TAG, "Shutdown broadcast timed out");
212 break;
213 }
214 try {
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800215 mActionDoneSync.wait(delay);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700216 } catch (InterruptedException e) {
217 }
218 }
219 }
220
221 Log.i(TAG, "Shutting down activity manager...");
222
223 final IActivityManager am =
224 ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
225 if (am != null) {
226 try {
227 am.shutdown(MAX_BROADCAST_TIME);
228 } catch (RemoteException e) {
229 }
230 }
231
232 final ITelephony phone =
233 ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
Nick Pellybd022f42009-08-14 18:33:38 -0700234 final IBluetooth bluetooth =
235 IBluetooth.Stub.asInterface(ServiceManager.checkService(
Nick Pellyf242b7b2009-10-08 00:12:45 +0200236 BluetoothAdapter.BLUETOOTH_SERVICE));
San Mehat9f7f7ca2010-01-07 11:34:59 -0800237
238 final IMountService mount =
239 IMountService.Stub.asInterface(
240 ServiceManager.checkService("mount"));
Dianne Hackborn55280a92009-05-07 15:53:46 -0700241
242 try {
243 bluetoothOff = bluetooth == null ||
Nick Pellyde893f52009-09-08 13:15:33 -0700244 bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700245 if (!bluetoothOff) {
246 Log.w(TAG, "Disabling Bluetooth...");
247 bluetooth.disable(false); // disable but don't persist new state
248 }
249 } catch (RemoteException ex) {
250 Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
251 bluetoothOff = true;
252 }
253
254 try {
255 radioOff = phone == null || !phone.isRadioOn();
256 if (!radioOff) {
257 Log.w(TAG, "Turning off radio...");
258 phone.setRadio(false);
259 }
260 } catch (RemoteException ex) {
261 Log.e(TAG, "RemoteException during radio shutdown", ex);
262 radioOff = true;
263 }
264
265 Log.i(TAG, "Waiting for Bluetooth and Radio...");
266
267 // Wait a max of 32 seconds for clean shutdown
268 for (int i = 0; i < MAX_NUM_PHONE_STATE_READS; i++) {
269 if (!bluetoothOff) {
270 try {
271 bluetoothOff =
Nick Pellyde893f52009-09-08 13:15:33 -0700272 bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700273 } catch (RemoteException ex) {
274 Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
275 bluetoothOff = true;
276 }
277 }
278 if (!radioOff) {
279 try {
280 radioOff = !phone.isRadioOn();
281 } catch (RemoteException ex) {
282 Log.e(TAG, "RemoteException during radio shutdown", ex);
283 radioOff = true;
284 }
285 }
286 if (radioOff && bluetoothOff) {
287 Log.i(TAG, "Radio and Bluetooth shutdown complete.");
288 break;
289 }
290 SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
291 }
292
San Mehat9f7f7ca2010-01-07 11:34:59 -0800293 // Shutdown MountService to ensure media is in a safe state
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800294 IMountShutdownObserver observer = new IMountShutdownObserver.Stub() {
295 public void onShutDownComplete(int statusCode) throws RemoteException {
296 Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown");
297 actionDone();
San Mehat9f7f7ca2010-01-07 11:34:59 -0800298 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800299 };
300
301 Log.i(TAG, "Shutting down MountService");
302 // Set initial variables and time out time.
303 mActionDone = false;
Mike Lockwood098e58d2010-05-13 16:29:49 -0400304 final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800305 synchronized (mActionDoneSync) {
306 try {
307 if (mount != null) {
308 mount.shutdown(observer);
309 } else {
310 Log.w(TAG, "MountService unavailable for shutdown");
311 }
312 } catch (Exception e) {
313 Log.e(TAG, "Exception during MountService shutdown", e);
314 }
315 while (!mActionDone) {
Mike Lockwood098e58d2010-05-13 16:29:49 -0400316 long delay = endShutTime - SystemClock.elapsedRealtime();
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800317 if (delay <= 0) {
318 Log.w(TAG, "Shutdown wait timed out");
319 break;
320 }
321 try {
322 mActionDoneSync.wait(delay);
323 } catch (InterruptedException e) {
324 }
325 }
San Mehat9f7f7ca2010-01-07 11:34:59 -0800326 }
327
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800328 if (mReboot) {
329 Log.i(TAG, "Rebooting, reason: " + mRebootReason);
330 try {
331 Power.reboot(mRebootReason);
332 } catch (Exception e) {
333 Log.e(TAG, "Reboot failed, will attempt shutdown instead", e);
334 }
Mike Lockwooda717f642010-04-01 20:01:44 -0700335 } else if (SHUTDOWN_VIBRATE_MS > 0) {
336 // vibrate before shutting down
337 Vibrator vibrator = new Vibrator();
338 vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
339 // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
340 try {
341 Thread.sleep(SHUTDOWN_VIBRATE_MS);
342 } catch (InterruptedException e) {
343 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800344 }
345
346 // Shutdown power
Dianne Hackborn55280a92009-05-07 15:53:46 -0700347 Log.i(TAG, "Performing low-level shutdown...");
348 Power.shutdown();
349 }
350}