• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
18 package com.android.internal.app;
19 
20 import android.app.ActivityManagerNative;
21 import android.app.IActivityManager;
22 import android.app.ProgressDialog;
23 import android.app.AlertDialog;
24 import android.bluetooth.BluetoothAdapter;
25 import android.bluetooth.IBluetooth;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.Intent;
30 import android.os.Handler;
31 import android.os.Power;
32 import android.os.PowerManager;
33 import android.os.RemoteException;
34 import android.os.ServiceManager;
35 import android.os.SystemClock;
36 import android.os.SystemProperties;
37 import android.os.Vibrator;
38 import android.os.storage.IMountService;
39 import android.os.storage.IMountShutdownObserver;
40 
41 import com.android.internal.telephony.ITelephony;
42 import android.util.Log;
43 import android.view.WindowManager;
44 
45 public final class ShutdownThread extends Thread {
46     // constants
47     private static final String TAG = "ShutdownThread";
48     private static final int MAX_NUM_PHONE_STATE_READS = 16;
49     private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
50     // maximum time we wait for the shutdown broadcast before going on.
51     private static final int MAX_BROADCAST_TIME = 10*1000;
52     private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
53 
54     // length of vibration before shutting down
55     private static final int SHUTDOWN_VIBRATE_MS = 500;
56 
57     // state tracking
58     private static Object sIsStartedGuard = new Object();
59     private static boolean sIsStarted = false;
60 
61     private static boolean mReboot;
62     private static String mRebootReason;
63 
64     // Provides shutdown assurance in case the system_server is killed
65     public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested";
66 
67     // static instance of this thread
68     private static final ShutdownThread sInstance = new ShutdownThread();
69 
70     private final Object mActionDoneSync = new Object();
71     private boolean mActionDone;
72     private Context mContext;
73     private PowerManager mPowerManager;
74     private PowerManager.WakeLock mWakeLock;
75     private Handler mHandler;
76 
ShutdownThread()77     private ShutdownThread() {
78     }
79 
80     /**
81      * Request a clean shutdown, waiting for subsystems to clean up their
82      * state etc.  Must be called from a Looper thread in which its UI
83      * is shown.
84      *
85      * @param context Context used to display the shutdown progress dialog.
86      * @param confirm true if user confirmation is needed before shutting down.
87      */
shutdown(final Context context, boolean confirm)88     public static void shutdown(final Context context, boolean confirm) {
89         // ensure that only one thread is trying to power down.
90         // any additional calls are just returned
91         synchronized (sIsStartedGuard) {
92             if (sIsStarted) {
93                 Log.d(TAG, "Request to shutdown already running, returning.");
94                 return;
95             }
96         }
97 
98         Log.d(TAG, "Notifying thread to start radio shutdown");
99 
100         if (confirm) {
101             final AlertDialog dialog = new AlertDialog.Builder(context)
102                     .setIcon(android.R.drawable.ic_dialog_alert)
103                     .setTitle(com.android.internal.R.string.power_off)
104                     .setMessage(com.android.internal.R.string.shutdown_confirm)
105                     .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
106                         public void onClick(DialogInterface dialog, int which) {
107                             beginShutdownSequence(context);
108                         }
109                     })
110                     .setNegativeButton(com.android.internal.R.string.no, null)
111                     .create();
112             dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
113             if (!context.getResources().getBoolean(
114                     com.android.internal.R.bool.config_sf_slowBlur)) {
115                 dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
116             }
117             dialog.show();
118         } else {
119             beginShutdownSequence(context);
120         }
121     }
122 
123     /**
124      * Request a clean shutdown, waiting for subsystems to clean up their
125      * state etc.  Must be called from a Looper thread in which its UI
126      * is shown.
127      *
128      * @param context Context used to display the shutdown progress dialog.
129      * @param reason code to pass to the kernel (e.g. "recovery"), or null.
130      * @param confirm true if user confirmation is needed before shutting down.
131      */
reboot(final Context context, String reason, boolean confirm)132     public static void reboot(final Context context, String reason, boolean confirm) {
133         mReboot = true;
134         mRebootReason = reason;
135         shutdown(context, confirm);
136     }
137 
beginShutdownSequence(Context context)138     private static void beginShutdownSequence(Context context) {
139         synchronized (sIsStartedGuard) {
140             if (sIsStarted) {
141                 Log.d(TAG, "Request to shutdown already running, returning.");
142                 return;
143             }
144             sIsStarted = true;
145         }
146 
147         // throw up an indeterminate system dialog to indicate radio is
148         // shutting down.
149         ProgressDialog pd = new ProgressDialog(context);
150         pd.setTitle(context.getText(com.android.internal.R.string.power_off));
151         pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
152         pd.setIndeterminate(true);
153         pd.setCancelable(false);
154         pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
155         if (!context.getResources().getBoolean(
156                 com.android.internal.R.bool.config_sf_slowBlur)) {
157             pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
158         }
159 
160         pd.show();
161 
162         // start the thread that initiates shutdown
163         sInstance.mContext = context;
164         sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
165         sInstance.mWakeLock = null;
166         if (sInstance.mPowerManager.isScreenOn()) {
167             try {
168                 sInstance.mWakeLock = sInstance.mPowerManager.newWakeLock(
169                         PowerManager.FULL_WAKE_LOCK, "Shutdown");
170                 sInstance.mWakeLock.acquire();
171             } catch (SecurityException e) {
172                 Log.w(TAG, "No permission to acquire wake lock", e);
173                 sInstance.mWakeLock = null;
174             }
175         }
176         sInstance.mHandler = new Handler() {
177         };
178         sInstance.start();
179     }
180 
actionDone()181     void actionDone() {
182         synchronized (mActionDoneSync) {
183             mActionDone = true;
184             mActionDoneSync.notifyAll();
185         }
186     }
187 
188     /**
189      * Makes sure we handle the shutdown gracefully.
190      * Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
191      */
run()192     public void run() {
193         boolean bluetoothOff;
194         boolean radioOff;
195 
196         BroadcastReceiver br = new BroadcastReceiver() {
197             @Override public void onReceive(Context context, Intent intent) {
198                 // We don't allow apps to cancel this, so ignore the result.
199                 actionDone();
200             }
201         };
202 
203         /*
204          * Write a system property in case the system_server reboots before we
205          * get to the actual hardware restart. If that happens, we'll retry at
206          * the beginning of the SystemServer startup.
207          */
208         {
209             String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : "");
210             SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
211         }
212 
213         Log.i(TAG, "Sending shutdown broadcast...");
214 
215         // First send the high-level shut down broadcast.
216         mActionDone = false;
217         mContext.sendOrderedBroadcast(new Intent(Intent.ACTION_SHUTDOWN), null,
218                 br, mHandler, 0, null, null);
219 
220         final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
221         synchronized (mActionDoneSync) {
222             while (!mActionDone) {
223                 long delay = endTime - SystemClock.elapsedRealtime();
224                 if (delay <= 0) {
225                     Log.w(TAG, "Shutdown broadcast timed out");
226                     break;
227                 }
228                 try {
229                     mActionDoneSync.wait(delay);
230                 } catch (InterruptedException e) {
231                 }
232             }
233         }
234 
235         Log.i(TAG, "Shutting down activity manager...");
236 
237         final IActivityManager am =
238             ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
239         if (am != null) {
240             try {
241                 am.shutdown(MAX_BROADCAST_TIME);
242             } catch (RemoteException e) {
243             }
244         }
245 
246         final ITelephony phone =
247                 ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
248         final IBluetooth bluetooth =
249                 IBluetooth.Stub.asInterface(ServiceManager.checkService(
250                         BluetoothAdapter.BLUETOOTH_SERVICE));
251 
252         final IMountService mount =
253                 IMountService.Stub.asInterface(
254                         ServiceManager.checkService("mount"));
255 
256         try {
257             bluetoothOff = bluetooth == null ||
258                            bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF;
259             if (!bluetoothOff) {
260                 Log.w(TAG, "Disabling Bluetooth...");
261                 bluetooth.disable(false);  // disable but don't persist new state
262             }
263         } catch (RemoteException ex) {
264             Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
265             bluetoothOff = true;
266         }
267 
268         try {
269             radioOff = phone == null || !phone.isRadioOn();
270             if (!radioOff) {
271                 Log.w(TAG, "Turning off radio...");
272                 phone.setRadio(false);
273             }
274         } catch (RemoteException ex) {
275             Log.e(TAG, "RemoteException during radio shutdown", ex);
276             radioOff = true;
277         }
278 
279         Log.i(TAG, "Waiting for Bluetooth and Radio...");
280 
281         // Wait a max of 32 seconds for clean shutdown
282         for (int i = 0; i < MAX_NUM_PHONE_STATE_READS; i++) {
283             if (!bluetoothOff) {
284                 try {
285                     bluetoothOff =
286                             bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF;
287                 } catch (RemoteException ex) {
288                     Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
289                     bluetoothOff = true;
290                 }
291             }
292             if (!radioOff) {
293                 try {
294                     radioOff = !phone.isRadioOn();
295                 } catch (RemoteException ex) {
296                     Log.e(TAG, "RemoteException during radio shutdown", ex);
297                     radioOff = true;
298                 }
299             }
300             if (radioOff && bluetoothOff) {
301                 Log.i(TAG, "Radio and Bluetooth shutdown complete.");
302                 break;
303             }
304             SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
305         }
306 
307         // Shutdown MountService to ensure media is in a safe state
308         IMountShutdownObserver observer = new IMountShutdownObserver.Stub() {
309             public void onShutDownComplete(int statusCode) throws RemoteException {
310                 Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown");
311                 actionDone();
312             }
313         };
314 
315         Log.i(TAG, "Shutting down MountService");
316         // Set initial variables and time out time.
317         mActionDone = false;
318         final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
319         synchronized (mActionDoneSync) {
320             try {
321                 if (mount != null) {
322                     mount.shutdown(observer);
323                 } else {
324                     Log.w(TAG, "MountService unavailable for shutdown");
325                 }
326             } catch (Exception e) {
327                 Log.e(TAG, "Exception during MountService shutdown", e);
328             }
329             while (!mActionDone) {
330                 long delay = endShutTime - SystemClock.elapsedRealtime();
331                 if (delay <= 0) {
332                     Log.w(TAG, "Shutdown wait timed out");
333                     break;
334                 }
335                 try {
336                     mActionDoneSync.wait(delay);
337                 } catch (InterruptedException e) {
338                 }
339             }
340         }
341 
342         rebootOrShutdown(mReboot, mRebootReason);
343     }
344 
345     /**
346      * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
347      * or {@link #shutdown(Context, boolean)} instead.
348      *
349      * @param reboot true to reboot or false to shutdown
350      * @param reason reason for reboot
351      */
rebootOrShutdown(boolean reboot, String reason)352     public static void rebootOrShutdown(boolean reboot, String reason) {
353         if (reboot) {
354             Log.i(TAG, "Rebooting, reason: " + reason);
355             try {
356                 Power.reboot(reason);
357             } catch (Exception e) {
358                 Log.e(TAG, "Reboot failed, will attempt shutdown instead", e);
359             }
360         } else if (SHUTDOWN_VIBRATE_MS > 0) {
361             // vibrate before shutting down
362             Vibrator vibrator = new Vibrator();
363             try {
364                 vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
365             } catch (Exception e) {
366                 // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
367                 Log.w(TAG, "Failed to vibrate during shutdown.", e);
368             }
369 
370             // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
371             try {
372                 Thread.sleep(SHUTDOWN_VIBRATE_MS);
373             } catch (InterruptedException unused) {
374             }
375         }
376 
377         // Shutdown power
378         Log.i(TAG, "Performing low-level shutdown...");
379         Power.shutdown();
380     }
381 }
382