ShutdownThread.java revision 9158825f9c41869689d6b1786d7c7aa8bdd524ce
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.IBluetoothManager;
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.UserHandle;
41import android.os.Vibrator;
42import android.os.SystemVibrator;
43import android.os.storage.IMountService;
44import android.os.storage.IMountShutdownObserver;
45
46import com.android.internal.telephony.ITelephony;
47
48import android.util.Log;
49import android.view.WindowManager;
50
51public 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
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     */
101    public static void shutdown(final Context context, boolean confirm) {
102        mReboot = false;
103        mRebootSafeMode = false;
104        shutdownInner(context, confirm);
105    }
106
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
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
165        public void onReceive(Context context, Intent intent) {
166            dialog.cancel();
167        }
168
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     */
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     */
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
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
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     */
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
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     */
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