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