blob: 51cd0f818ae75d035f3a00e4e190fdf2051bc960 [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;
San Mehatb1043402010-02-05 08:26:50 -080036import android.os.storage.IMountService;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080037import android.os.storage.IMountShutdownObserver;
Dianne Hackborn568cae52009-10-07 16:13:39 -070038
Dianne Hackborn55280a92009-05-07 15:53:46 -070039import com.android.internal.telephony.ITelephony;
40import android.util.Log;
41import android.view.WindowManager;
42
43public final class ShutdownThread extends Thread {
44 // constants
45 private static final String TAG = "ShutdownThread";
46 private static final int MAX_NUM_PHONE_STATE_READS = 16;
47 private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
48 // maximum time we wait for the shutdown broadcast before going on.
49 private static final int MAX_BROADCAST_TIME = 10*1000;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080050 private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
Dianne Hackborn55280a92009-05-07 15:53:46 -070051
52 // state tracking
53 private static Object sIsStartedGuard = new Object();
54 private static boolean sIsStarted = false;
55
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080056 private static boolean mReboot;
57 private static String mRebootReason;
58
Dianne Hackborn55280a92009-05-07 15:53:46 -070059 // static instance of this thread
60 private static final ShutdownThread sInstance = new ShutdownThread();
61
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080062 private final Object mActionDoneSync = new Object();
63 private boolean mActionDone;
Dianne Hackborn55280a92009-05-07 15:53:46 -070064 private Context mContext;
Dianne Hackbornf99ae762010-03-08 12:43:51 -080065 private PowerManager mPowerManager;
66 private PowerManager.WakeLock mWakeLock;
Dianne Hackborn55280a92009-05-07 15:53:46 -070067 private Handler mHandler;
68
69 private ShutdownThread() {
70 }
71
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080072 /**
Dianne Hackborn55280a92009-05-07 15:53:46 -070073 * Request a clean shutdown, waiting for subsystems to clean up their
74 * state etc. Must be called from a Looper thread in which its UI
75 * is shown.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080076 *
Dianne Hackborn55280a92009-05-07 15:53:46 -070077 * @param context Context used to display the shutdown progress dialog.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080078 * @param confirm true if user confirmation is needed before shutting down.
Dianne Hackborn55280a92009-05-07 15:53:46 -070079 */
80 public static void shutdown(final Context context, boolean confirm) {
81 // ensure that only one thread is trying to power down.
82 // any additional calls are just returned
83 synchronized (sIsStartedGuard){
84 if (sIsStarted) {
85 Log.d(TAG, "Request to shutdown already running, returning.");
86 return;
87 }
88 }
89
90 Log.d(TAG, "Notifying thread to start radio shutdown");
91
92 if (confirm) {
93 final AlertDialog dialog = new AlertDialog.Builder(context)
94 .setIcon(android.R.drawable.ic_dialog_alert)
95 .setTitle(com.android.internal.R.string.power_off)
96 .setMessage(com.android.internal.R.string.shutdown_confirm)
97 .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
98 public void onClick(DialogInterface dialog, int which) {
99 beginShutdownSequence(context);
100 }
101 })
102 .setNegativeButton(com.android.internal.R.string.no, null)
103 .create();
104 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
Dianne Hackborn568cae52009-10-07 16:13:39 -0700105 if (!context.getResources().getBoolean(
106 com.android.internal.R.bool.config_sf_slowBlur)) {
107 dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
108 }
Dianne Hackborn55280a92009-05-07 15:53:46 -0700109 dialog.show();
110 } else {
111 beginShutdownSequence(context);
112 }
113 }
114
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800115 /**
116 * Request a clean shutdown, waiting for subsystems to clean up their
117 * state etc. Must be called from a Looper thread in which its UI
118 * is shown.
119 *
120 * @param context Context used to display the shutdown progress dialog.
121 * @param reason code to pass to the kernel (e.g. "recovery"), or null.
122 * @param confirm true if user confirmation is needed before shutting down.
123 */
124 public static void reboot(final Context context, String reason, boolean confirm) {
125 mReboot = true;
126 mRebootReason = reason;
127 shutdown(context, confirm);
128 }
129
Dianne Hackborn55280a92009-05-07 15:53:46 -0700130 private static void beginShutdownSequence(Context context) {
131 synchronized (sIsStartedGuard) {
132 sIsStarted = true;
133 }
134
135 // throw up an indeterminate system dialog to indicate radio is
136 // shutting down.
137 ProgressDialog pd = new ProgressDialog(context);
138 pd.setTitle(context.getText(com.android.internal.R.string.power_off));
139 pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
140 pd.setIndeterminate(true);
141 pd.setCancelable(false);
142 pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
Dianne Hackborn568cae52009-10-07 16:13:39 -0700143 if (!context.getResources().getBoolean(
144 com.android.internal.R.bool.config_sf_slowBlur)) {
145 pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
146 }
Dianne Hackborn55280a92009-05-07 15:53:46 -0700147
148 pd.show();
149
150 // start the thread that initiates shutdown
151 sInstance.mContext = context;
Dianne Hackbornf99ae762010-03-08 12:43:51 -0800152 sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
153 sInstance.mWakeLock = null;
154 if (sInstance.mPowerManager.isScreenOn()) {
155 try {
156 sInstance.mWakeLock = sInstance.mPowerManager.newWakeLock(
157 PowerManager.FULL_WAKE_LOCK, "Shutdown");
158 sInstance.mWakeLock.acquire();
159 } catch (SecurityException e) {
160 Log.w(TAG, "No permission to acquire wake lock", e);
161 sInstance.mWakeLock = null;
162 }
163 }
Dianne Hackborn55280a92009-05-07 15:53:46 -0700164 sInstance.mHandler = new Handler() {
165 };
166 sInstance.start();
167 }
168
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800169 void actionDone() {
170 synchronized (mActionDoneSync) {
171 mActionDone = true;
172 mActionDoneSync.notifyAll();
Dianne Hackborn55280a92009-05-07 15:53:46 -0700173 }
174 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800175
Dianne Hackborn55280a92009-05-07 15:53:46 -0700176 /**
177 * Makes sure we handle the shutdown gracefully.
178 * Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
179 */
180 public void run() {
181 boolean bluetoothOff;
182 boolean radioOff;
183
184 BroadcastReceiver br = new BroadcastReceiver() {
185 @Override public void onReceive(Context context, Intent intent) {
186 // We don't allow apps to cancel this, so ignore the result.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800187 actionDone();
Dianne Hackborn55280a92009-05-07 15:53:46 -0700188 }
189 };
190
191 Log.i(TAG, "Sending shutdown broadcast...");
192
193 // First send the high-level shut down broadcast.
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800194 mActionDone = false;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700195 mContext.sendOrderedBroadcast(new Intent(Intent.ACTION_SHUTDOWN), null,
196 br, mHandler, 0, null, null);
197
198 final long endTime = System.currentTimeMillis() + MAX_BROADCAST_TIME;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800199 synchronized (mActionDoneSync) {
200 while (!mActionDone) {
Dianne Hackborn55280a92009-05-07 15:53:46 -0700201 long delay = endTime - System.currentTimeMillis();
202 if (delay <= 0) {
203 Log.w(TAG, "Shutdown broadcast timed out");
204 break;
205 }
206 try {
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800207 mActionDoneSync.wait(delay);
Dianne Hackborn55280a92009-05-07 15:53:46 -0700208 } catch (InterruptedException e) {
209 }
210 }
211 }
212
213 Log.i(TAG, "Shutting down activity manager...");
214
215 final IActivityManager am =
216 ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
217 if (am != null) {
218 try {
219 am.shutdown(MAX_BROADCAST_TIME);
220 } catch (RemoteException e) {
221 }
222 }
223
224 final ITelephony phone =
225 ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
Nick Pellybd022f42009-08-14 18:33:38 -0700226 final IBluetooth bluetooth =
227 IBluetooth.Stub.asInterface(ServiceManager.checkService(
Nick Pellyf242b7b2009-10-08 00:12:45 +0200228 BluetoothAdapter.BLUETOOTH_SERVICE));
San Mehat9f7f7ca2010-01-07 11:34:59 -0800229
230 final IMountService mount =
231 IMountService.Stub.asInterface(
232 ServiceManager.checkService("mount"));
Dianne Hackborn55280a92009-05-07 15:53:46 -0700233
234 try {
235 bluetoothOff = bluetooth == null ||
Nick Pellyde893f52009-09-08 13:15:33 -0700236 bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700237 if (!bluetoothOff) {
238 Log.w(TAG, "Disabling Bluetooth...");
239 bluetooth.disable(false); // disable but don't persist new state
240 }
241 } catch (RemoteException ex) {
242 Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
243 bluetoothOff = true;
244 }
245
246 try {
247 radioOff = phone == null || !phone.isRadioOn();
248 if (!radioOff) {
249 Log.w(TAG, "Turning off radio...");
250 phone.setRadio(false);
251 }
252 } catch (RemoteException ex) {
253 Log.e(TAG, "RemoteException during radio shutdown", ex);
254 radioOff = true;
255 }
256
257 Log.i(TAG, "Waiting for Bluetooth and Radio...");
258
259 // Wait a max of 32 seconds for clean shutdown
260 for (int i = 0; i < MAX_NUM_PHONE_STATE_READS; i++) {
261 if (!bluetoothOff) {
262 try {
263 bluetoothOff =
Nick Pellyde893f52009-09-08 13:15:33 -0700264 bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700265 } catch (RemoteException ex) {
266 Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
267 bluetoothOff = true;
268 }
269 }
270 if (!radioOff) {
271 try {
272 radioOff = !phone.isRadioOn();
273 } catch (RemoteException ex) {
274 Log.e(TAG, "RemoteException during radio shutdown", ex);
275 radioOff = true;
276 }
277 }
278 if (radioOff && bluetoothOff) {
279 Log.i(TAG, "Radio and Bluetooth shutdown complete.");
280 break;
281 }
282 SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
283 }
284
San Mehat9f7f7ca2010-01-07 11:34:59 -0800285 // Shutdown MountService to ensure media is in a safe state
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800286 IMountShutdownObserver observer = new IMountShutdownObserver.Stub() {
287 public void onShutDownComplete(int statusCode) throws RemoteException {
288 Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown");
289 actionDone();
San Mehat9f7f7ca2010-01-07 11:34:59 -0800290 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800291 };
292
293 Log.i(TAG, "Shutting down MountService");
294 // Set initial variables and time out time.
295 mActionDone = false;
296 final long endShutTime = System.currentTimeMillis() + MAX_SHUTDOWN_WAIT_TIME;
297 synchronized (mActionDoneSync) {
298 try {
299 if (mount != null) {
300 mount.shutdown(observer);
301 } else {
302 Log.w(TAG, "MountService unavailable for shutdown");
303 }
304 } catch (Exception e) {
305 Log.e(TAG, "Exception during MountService shutdown", e);
306 }
307 while (!mActionDone) {
308 long delay = endShutTime - System.currentTimeMillis();
309 if (delay <= 0) {
310 Log.w(TAG, "Shutdown wait timed out");
311 break;
312 }
313 try {
314 mActionDoneSync.wait(delay);
315 } catch (InterruptedException e) {
316 }
317 }
San Mehat9f7f7ca2010-01-07 11:34:59 -0800318 }
319
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800320 if (mReboot) {
321 Log.i(TAG, "Rebooting, reason: " + mRebootReason);
322 try {
323 Power.reboot(mRebootReason);
324 } catch (Exception e) {
325 Log.e(TAG, "Reboot failed, will attempt shutdown instead", e);
326 }
327 }
328
329 // Shutdown power
Dianne Hackborn55280a92009-05-07 15:53:46 -0700330 Log.i(TAG, "Performing low-level shutdown...");
331 Power.shutdown();
332 }
333}