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