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.AudioAttributes;
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;
48import com.android.server.pm.PackageManagerService;
49
50import android.util.Log;
51import android.view.WindowManager;
52
53public 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
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     */
108    public static void shutdown(final Context context, boolean confirm) {
109        mReboot = false;
110        mRebootSafeMode = false;
111        shutdownInner(context, confirm);
112    }
113
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
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
172        public void onReceive(Context context, Intent intent) {
173            dialog.cancel();
174        }
175
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     */
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     */
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
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
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     */
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
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     */
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