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