• 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.server.power;
19 
20 import android.app.ActivityManagerNative;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.app.IActivityManager;
24 import android.app.ProgressDialog;
25 import android.bluetooth.BluetoothAdapter;
26 import android.bluetooth.IBluetoothManager;
27 import android.nfc.NfcAdapter;
28 import android.nfc.INfcAdapter;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.DialogInterface;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.os.Handler;
35 import android.os.PowerManager;
36 import android.os.RemoteException;
37 import android.os.ServiceManager;
38 import android.os.SystemClock;
39 import android.os.SystemProperties;
40 import android.os.UserHandle;
41 import android.os.Vibrator;
42 import android.os.SystemVibrator;
43 import android.os.storage.IMountService;
44 import android.os.storage.IMountShutdownObserver;
45 
46 import com.android.internal.telephony.ITelephony;
47 
48 import android.util.Log;
49 import android.view.WindowManager;
50 
51 public final class ShutdownThread extends Thread {
52     // constants
53     private static final String TAG = "ShutdownThread";
54     private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
55     // maximum time we wait for the shutdown broadcast before going on.
56     private static final int MAX_BROADCAST_TIME = 10*1000;
57     private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
58     private static final int MAX_RADIO_WAIT_TIME = 12*1000;
59 
60     // length of vibration before shutting down
61     private static final int SHUTDOWN_VIBRATE_MS = 500;
62 
63     // state tracking
64     private static Object sIsStartedGuard = new Object();
65     private static boolean sIsStarted = false;
66 
67     private static boolean mReboot;
68     private static boolean mRebootSafeMode;
69     private static String mRebootReason;
70 
71     // Provides shutdown assurance in case the system_server is killed
72     public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested";
73 
74     // Indicates whether we are rebooting into safe mode
75     public static final String REBOOT_SAFEMODE_PROPERTY = "persist.sys.safemode";
76 
77     // static instance of this thread
78     private static final ShutdownThread sInstance = new ShutdownThread();
79 
80     private final Object mActionDoneSync = new Object();
81     private boolean mActionDone;
82     private Context mContext;
83     private PowerManager mPowerManager;
84     private PowerManager.WakeLock mCpuWakeLock;
85     private PowerManager.WakeLock mScreenWakeLock;
86     private Handler mHandler;
87 
88     private static AlertDialog sConfirmDialog;
89 
ShutdownThread()90     private ShutdownThread() {
91     }
92 
93     /**
94      * Request a clean shutdown, waiting for subsystems to clean up their
95      * state etc.  Must be called from a Looper thread in which its UI
96      * is shown.
97      *
98      * @param context Context used to display the shutdown progress dialog.
99      * @param confirm true if user confirmation is needed before shutting down.
100      */
shutdown(final Context context, boolean confirm)101     public static void shutdown(final Context context, boolean confirm) {
102         mReboot = false;
103         mRebootSafeMode = false;
104         shutdownInner(context, confirm);
105     }
106 
shutdownInner(final Context context, boolean confirm)107     static void shutdownInner(final Context context, boolean confirm) {
108         // ensure that only one thread is trying to power down.
109         // any additional calls are just returned
110         synchronized (sIsStartedGuard) {
111             if (sIsStarted) {
112                 Log.d(TAG, "Request to shutdown already running, returning.");
113                 return;
114             }
115         }
116 
117         final int longPressBehavior = context.getResources().getInteger(
118                         com.android.internal.R.integer.config_longPressOnPowerBehavior);
119         final int resourceId = mRebootSafeMode
120                 ? com.android.internal.R.string.reboot_safemode_confirm
121                 : (longPressBehavior == 2
122                         ? com.android.internal.R.string.shutdown_confirm_question
123                         : com.android.internal.R.string.shutdown_confirm);
124 
125         Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
126 
127         if (confirm) {
128             final CloseDialogReceiver closer = new CloseDialogReceiver(context);
129             if (sConfirmDialog != null) {
130                 sConfirmDialog.dismiss();
131             }
132             sConfirmDialog = new AlertDialog.Builder(context)
133                     .setTitle(mRebootSafeMode
134                             ? com.android.internal.R.string.reboot_safemode_title
135                             : com.android.internal.R.string.power_off)
136                     .setMessage(resourceId)
137                     .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
138                         public void onClick(DialogInterface dialog, int which) {
139                             beginShutdownSequence(context);
140                         }
141                     })
142                     .setNegativeButton(com.android.internal.R.string.no, null)
143                     .create();
144             closer.dialog = sConfirmDialog;
145             sConfirmDialog.setOnDismissListener(closer);
146             sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
147             sConfirmDialog.show();
148         } else {
149             beginShutdownSequence(context);
150         }
151     }
152 
153     private static class CloseDialogReceiver extends BroadcastReceiver
154             implements DialogInterface.OnDismissListener {
155         private Context mContext;
156         public Dialog dialog;
157 
CloseDialogReceiver(Context context)158         CloseDialogReceiver(Context context) {
159             mContext = context;
160             IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
161             context.registerReceiver(this, filter);
162         }
163 
164         @Override
onReceive(Context context, Intent intent)165         public void onReceive(Context context, Intent intent) {
166             dialog.cancel();
167         }
168 
onDismiss(DialogInterface unused)169         public void onDismiss(DialogInterface unused) {
170             mContext.unregisterReceiver(this);
171         }
172     }
173 
174     /**
175      * Request a clean shutdown, waiting for subsystems to clean up their
176      * state etc.  Must be called from a Looper thread in which its UI
177      * is shown.
178      *
179      * @param context Context used to display the shutdown progress dialog.
180      * @param reason code to pass to the kernel (e.g. "recovery"), or null.
181      * @param confirm true if user confirmation is needed before shutting down.
182      */
reboot(final Context context, String reason, boolean confirm)183     public static void reboot(final Context context, String reason, boolean confirm) {
184         mReboot = true;
185         mRebootSafeMode = false;
186         mRebootReason = reason;
187         shutdownInner(context, confirm);
188     }
189 
190     /**
191      * Request a reboot into safe mode.  Must be called from a Looper thread in which its UI
192      * is shown.
193      *
194      * @param context Context used to display the shutdown progress dialog.
195      * @param confirm true if user confirmation is needed before shutting down.
196      */
rebootSafeMode(final Context context, boolean confirm)197     public static void rebootSafeMode(final Context context, boolean confirm) {
198         mReboot = true;
199         mRebootSafeMode = true;
200         mRebootReason = null;
201         shutdownInner(context, confirm);
202     }
203 
beginShutdownSequence(Context context)204     private static void beginShutdownSequence(Context context) {
205         synchronized (sIsStartedGuard) {
206             if (sIsStarted) {
207                 Log.d(TAG, "Shutdown sequence already running, returning.");
208                 return;
209             }
210             sIsStarted = true;
211         }
212 
213         // throw up an indeterminate system dialog to indicate radio is
214         // shutting down.
215         ProgressDialog pd = new ProgressDialog(context);
216         pd.setTitle(context.getText(com.android.internal.R.string.power_off));
217         pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
218         pd.setIndeterminate(true);
219         pd.setCancelable(false);
220         pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
221 
222         pd.show();
223 
224         sInstance.mContext = context;
225         sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
226 
227         // make sure we never fall asleep again
228         sInstance.mCpuWakeLock = null;
229         try {
230             sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
231                     PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
232             sInstance.mCpuWakeLock.setReferenceCounted(false);
233             sInstance.mCpuWakeLock.acquire();
234         } catch (SecurityException e) {
235             Log.w(TAG, "No permission to acquire wake lock", e);
236             sInstance.mCpuWakeLock = null;
237         }
238 
239         // also make sure the screen stays on for better user experience
240         sInstance.mScreenWakeLock = null;
241         if (sInstance.mPowerManager.isScreenOn()) {
242             try {
243                 sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
244                         PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
245                 sInstance.mScreenWakeLock.setReferenceCounted(false);
246                 sInstance.mScreenWakeLock.acquire();
247             } catch (SecurityException e) {
248                 Log.w(TAG, "No permission to acquire wake lock", e);
249                 sInstance.mScreenWakeLock = null;
250             }
251         }
252 
253         // start the thread that initiates shutdown
254         sInstance.mHandler = new Handler() {
255         };
256         sInstance.start();
257     }
258 
actionDone()259     void actionDone() {
260         synchronized (mActionDoneSync) {
261             mActionDone = true;
262             mActionDoneSync.notifyAll();
263         }
264     }
265 
266     /**
267      * Makes sure we handle the shutdown gracefully.
268      * Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
269      */
run()270     public void run() {
271         BroadcastReceiver br = new BroadcastReceiver() {
272             @Override public void onReceive(Context context, Intent intent) {
273                 // We don't allow apps to cancel this, so ignore the result.
274                 actionDone();
275             }
276         };
277 
278         /*
279          * Write a system property in case the system_server reboots before we
280          * get to the actual hardware restart. If that happens, we'll retry at
281          * the beginning of the SystemServer startup.
282          */
283         {
284             String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : "");
285             SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
286         }
287 
288         /*
289          * If we are rebooting into safe mode, write a system property
290          * indicating so.
291          */
292         if (mRebootSafeMode) {
293             SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
294         }
295 
296         Log.i(TAG, "Sending shutdown broadcast...");
297 
298         // First send the high-level shut down broadcast.
299         mActionDone = false;
300         Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
301         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
302         mContext.sendOrderedBroadcastAsUser(intent,
303                 UserHandle.ALL, null, br, mHandler, 0, null, null);
304 
305         final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
306         synchronized (mActionDoneSync) {
307             while (!mActionDone) {
308                 long delay = endTime - SystemClock.elapsedRealtime();
309                 if (delay <= 0) {
310                     Log.w(TAG, "Shutdown broadcast timed out");
311                     break;
312                 }
313                 try {
314                     mActionDoneSync.wait(delay);
315                 } catch (InterruptedException e) {
316                 }
317             }
318         }
319 
320         Log.i(TAG, "Shutting down activity manager...");
321 
322         final IActivityManager am =
323             ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
324         if (am != null) {
325             try {
326                 am.shutdown(MAX_BROADCAST_TIME);
327             } catch (RemoteException e) {
328             }
329         }
330 
331         // Shutdown radios.
332         shutdownRadios(MAX_RADIO_WAIT_TIME);
333 
334         // Shutdown MountService to ensure media is in a safe state
335         IMountShutdownObserver observer = new IMountShutdownObserver.Stub() {
336             public void onShutDownComplete(int statusCode) throws RemoteException {
337                 Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown");
338                 actionDone();
339             }
340         };
341 
342         Log.i(TAG, "Shutting down MountService");
343 
344         // Set initial variables and time out time.
345         mActionDone = false;
346         final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
347         synchronized (mActionDoneSync) {
348             try {
349                 final IMountService mount = IMountService.Stub.asInterface(
350                         ServiceManager.checkService("mount"));
351                 if (mount != null) {
352                     mount.shutdown(observer);
353                 } else {
354                     Log.w(TAG, "MountService unavailable for shutdown");
355                 }
356             } catch (Exception e) {
357                 Log.e(TAG, "Exception during MountService shutdown", e);
358             }
359             while (!mActionDone) {
360                 long delay = endShutTime - SystemClock.elapsedRealtime();
361                 if (delay <= 0) {
362                     Log.w(TAG, "Shutdown wait timed out");
363                     break;
364                 }
365                 try {
366                     mActionDoneSync.wait(delay);
367                 } catch (InterruptedException e) {
368                 }
369             }
370         }
371 
372         rebootOrShutdown(mReboot, mRebootReason);
373     }
374 
shutdownRadios(int timeout)375     private void shutdownRadios(int timeout) {
376         // If a radio is wedged, disabling it may hang so we do this work in another thread,
377         // just in case.
378         final long endTime = SystemClock.elapsedRealtime() + timeout;
379         final boolean[] done = new boolean[1];
380         Thread t = new Thread() {
381             public void run() {
382                 boolean nfcOff;
383                 boolean bluetoothOff;
384                 boolean radioOff;
385 
386                 final INfcAdapter nfc =
387                         INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc"));
388                 final ITelephony phone =
389                         ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
390                 final IBluetoothManager bluetooth =
391                         IBluetoothManager.Stub.asInterface(ServiceManager.checkService(
392                                 BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE));
393 
394                 try {
395                     nfcOff = nfc == null ||
396                              nfc.getState() == NfcAdapter.STATE_OFF;
397                     if (!nfcOff) {
398                         Log.w(TAG, "Turning off NFC...");
399                         nfc.disable(false); // Don't persist new state
400                     }
401                 } catch (RemoteException ex) {
402                 Log.e(TAG, "RemoteException during NFC shutdown", ex);
403                     nfcOff = true;
404                 }
405 
406                 try {
407                     bluetoothOff = bluetooth == null || !bluetooth.isEnabled();
408                     if (!bluetoothOff) {
409                         Log.w(TAG, "Disabling Bluetooth...");
410                         bluetooth.disable(false);  // disable but don't persist new state
411                     }
412                 } catch (RemoteException ex) {
413                     Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
414                     bluetoothOff = true;
415                 }
416 
417                 try {
418                     radioOff = phone == null || !phone.isRadioOn();
419                     if (!radioOff) {
420                         Log.w(TAG, "Turning off radio...");
421                         phone.setRadio(false);
422                     }
423                 } catch (RemoteException ex) {
424                     Log.e(TAG, "RemoteException during radio shutdown", ex);
425                     radioOff = true;
426                 }
427 
428                 Log.i(TAG, "Waiting for NFC, Bluetooth and Radio...");
429 
430                 while (SystemClock.elapsedRealtime() < endTime) {
431                     if (!bluetoothOff) {
432                         try {
433                             bluetoothOff = !bluetooth.isEnabled();
434                         } catch (RemoteException ex) {
435                             Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
436                             bluetoothOff = true;
437                         }
438                         if (bluetoothOff) {
439                             Log.i(TAG, "Bluetooth turned off.");
440                         }
441                     }
442                     if (!radioOff) {
443                         try {
444                             radioOff = !phone.isRadioOn();
445                         } catch (RemoteException ex) {
446                             Log.e(TAG, "RemoteException during radio shutdown", ex);
447                             radioOff = true;
448                         }
449                         if (radioOff) {
450                             Log.i(TAG, "Radio turned off.");
451                         }
452                     }
453                     if (!nfcOff) {
454                         try {
455                             nfcOff = nfc.getState() == NfcAdapter.STATE_OFF;
456                         } catch (RemoteException ex) {
457                             Log.e(TAG, "RemoteException during NFC shutdown", ex);
458                             nfcOff = true;
459                         }
460                         if (radioOff) {
461                             Log.i(TAG, "NFC turned off.");
462                         }
463                     }
464 
465                     if (radioOff && bluetoothOff && nfcOff) {
466                         Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete.");
467                         done[0] = true;
468                         break;
469                     }
470                     SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
471                 }
472             }
473         };
474 
475         t.start();
476         try {
477             t.join(timeout);
478         } catch (InterruptedException ex) {
479         }
480         if (!done[0]) {
481             Log.w(TAG, "Timed out waiting for NFC, Radio and Bluetooth shutdown.");
482         }
483     }
484 
485     /**
486      * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
487      * or {@link #shutdown(Context, boolean)} instead.
488      *
489      * @param reboot true to reboot or false to shutdown
490      * @param reason reason for reboot
491      */
rebootOrShutdown(boolean reboot, String reason)492     public static void rebootOrShutdown(boolean reboot, String reason) {
493         if (reboot) {
494             Log.i(TAG, "Rebooting, reason: " + reason);
495             PowerManagerService.lowLevelReboot(reason);
496             Log.e(TAG, "Reboot failed, will attempt shutdown instead");
497         } else if (SHUTDOWN_VIBRATE_MS > 0) {
498             // vibrate before shutting down
499             Vibrator vibrator = new SystemVibrator();
500             try {
501                 vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
502             } catch (Exception e) {
503                 // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
504                 Log.w(TAG, "Failed to vibrate during shutdown.", e);
505             }
506 
507             // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
508             try {
509                 Thread.sleep(SHUTDOWN_VIBRATE_MS);
510             } catch (InterruptedException unused) {
511             }
512         }
513 
514         // Shutdown power
515         Log.i(TAG, "Performing low-level shutdown...");
516         PowerManagerService.lowLevelShutdown();
517     }
518 }
519