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