blob: 77d6e20a0fa5161b5cfaaf2e1fe4e8a2f42bf52b [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;
24import android.bluetooth.BluetoothDevice;
25import android.bluetooth.IBluetoothDevice;
26import android.content.BroadcastReceiver;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.Intent;
30import android.os.Handler;
31import android.os.RemoteException;
32import android.os.Power;
33import android.os.ServiceManager;
34import android.os.SystemClock;
35import com.android.internal.telephony.ITelephony;
36import android.util.Log;
37import android.view.WindowManager;
38
39public final class ShutdownThread extends Thread {
40 // constants
41 private static final String TAG = "ShutdownThread";
42 private static final int MAX_NUM_PHONE_STATE_READS = 16;
43 private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
44 // maximum time we wait for the shutdown broadcast before going on.
45 private static final int MAX_BROADCAST_TIME = 10*1000;
46
47 // state tracking
48 private static Object sIsStartedGuard = new Object();
49 private static boolean sIsStarted = false;
50
51 // static instance of this thread
52 private static final ShutdownThread sInstance = new ShutdownThread();
53
54 private final Object mBroadcastDoneSync = new Object();
55 private boolean mBroadcastDone;
56 private Context mContext;
57 private Handler mHandler;
58
59 private ShutdownThread() {
60 }
61
62 /**
63 * Request a clean shutdown, waiting for subsystems to clean up their
64 * state etc. Must be called from a Looper thread in which its UI
65 * is shown.
66 *
67 * @param context Context used to display the shutdown progress dialog.
68 */
69 public static void shutdown(final Context context, boolean confirm) {
70 // ensure that only one thread is trying to power down.
71 // any additional calls are just returned
72 synchronized (sIsStartedGuard){
73 if (sIsStarted) {
74 Log.d(TAG, "Request to shutdown already running, returning.");
75 return;
76 }
77 }
78
79 Log.d(TAG, "Notifying thread to start radio shutdown");
80
81 if (confirm) {
82 final AlertDialog dialog = new AlertDialog.Builder(context)
83 .setIcon(android.R.drawable.ic_dialog_alert)
84 .setTitle(com.android.internal.R.string.power_off)
85 .setMessage(com.android.internal.R.string.shutdown_confirm)
86 .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
87 public void onClick(DialogInterface dialog, int which) {
88 beginShutdownSequence(context);
89 }
90 })
91 .setNegativeButton(com.android.internal.R.string.no, null)
92 .create();
93 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
94 dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
95 dialog.show();
96 } else {
97 beginShutdownSequence(context);
98 }
99 }
100
101 private static void beginShutdownSequence(Context context) {
102 synchronized (sIsStartedGuard) {
103 sIsStarted = true;
104 }
105
106 // throw up an indeterminate system dialog to indicate radio is
107 // shutting down.
108 ProgressDialog pd = new ProgressDialog(context);
109 pd.setTitle(context.getText(com.android.internal.R.string.power_off));
110 pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
111 pd.setIndeterminate(true);
112 pd.setCancelable(false);
113 pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
114 pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
115
116 pd.show();
117
118 // start the thread that initiates shutdown
119 sInstance.mContext = context;
120 sInstance.mHandler = new Handler() {
121 };
122 sInstance.start();
123 }
124
125 void broadcastDone() {
126 synchronized (mBroadcastDoneSync) {
127 mBroadcastDone = true;
128 mBroadcastDoneSync.notifyAll();
129 }
130 }
131
132 /**
133 * Makes sure we handle the shutdown gracefully.
134 * Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
135 */
136 public void run() {
137 boolean bluetoothOff;
138 boolean radioOff;
139
140 BroadcastReceiver br = new BroadcastReceiver() {
141 @Override public void onReceive(Context context, Intent intent) {
142 // We don't allow apps to cancel this, so ignore the result.
143 broadcastDone();
144 }
145 };
146
147 Log.i(TAG, "Sending shutdown broadcast...");
148
149 // First send the high-level shut down broadcast.
150 mBroadcastDone = false;
151 mContext.sendOrderedBroadcast(new Intent(Intent.ACTION_SHUTDOWN), null,
152 br, mHandler, 0, null, null);
153
154 final long endTime = System.currentTimeMillis() + MAX_BROADCAST_TIME;
155 synchronized (mBroadcastDoneSync) {
156 while (!mBroadcastDone) {
157 long delay = endTime - System.currentTimeMillis();
158 if (delay <= 0) {
159 Log.w(TAG, "Shutdown broadcast timed out");
160 break;
161 }
162 try {
163 mBroadcastDoneSync.wait(delay);
164 } catch (InterruptedException e) {
165 }
166 }
167 }
168
169 Log.i(TAG, "Shutting down activity manager...");
170
171 final IActivityManager am =
172 ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
173 if (am != null) {
174 try {
175 am.shutdown(MAX_BROADCAST_TIME);
176 } catch (RemoteException e) {
177 }
178 }
179
180 final ITelephony phone =
181 ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
182 final IBluetoothDevice bluetooth =
183 IBluetoothDevice.Stub.asInterface(ServiceManager.checkService(
184 Context.BLUETOOTH_SERVICE));
185
186 try {
187 bluetoothOff = bluetooth == null ||
188 bluetooth.getBluetoothState() == BluetoothDevice.BLUETOOTH_STATE_OFF;
189 if (!bluetoothOff) {
190 Log.w(TAG, "Disabling Bluetooth...");
191 bluetooth.disable(false); // disable but don't persist new state
192 }
193 } catch (RemoteException ex) {
194 Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
195 bluetoothOff = true;
196 }
197
198 try {
199 radioOff = phone == null || !phone.isRadioOn();
200 if (!radioOff) {
201 Log.w(TAG, "Turning off radio...");
202 phone.setRadio(false);
203 }
204 } catch (RemoteException ex) {
205 Log.e(TAG, "RemoteException during radio shutdown", ex);
206 radioOff = true;
207 }
208
209 Log.i(TAG, "Waiting for Bluetooth and Radio...");
210
211 // Wait a max of 32 seconds for clean shutdown
212 for (int i = 0; i < MAX_NUM_PHONE_STATE_READS; i++) {
213 if (!bluetoothOff) {
214 try {
215 bluetoothOff =
216 bluetooth.getBluetoothState() == BluetoothDevice.BLUETOOTH_STATE_OFF;
217 } catch (RemoteException ex) {
218 Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
219 bluetoothOff = true;
220 }
221 }
222 if (!radioOff) {
223 try {
224 radioOff = !phone.isRadioOn();
225 } catch (RemoteException ex) {
226 Log.e(TAG, "RemoteException during radio shutdown", ex);
227 radioOff = true;
228 }
229 }
230 if (radioOff && bluetoothOff) {
231 Log.i(TAG, "Radio and Bluetooth shutdown complete.");
232 break;
233 }
234 SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
235 }
236
237 //shutdown power
238 Log.i(TAG, "Performing low-level shutdown...");
239 Power.shutdown();
240 }
241}