ShutdownThread.java revision b4302182bc916ba0f5ee08756c1b2d15cb845874
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.AlertDialog;
21import android.app.Dialog;
22import android.app.IActivityManager;
23import android.app.KeyguardManager;
24import android.app.ProgressDialog;
25import android.app.WallpaperColors;
26import android.app.WallpaperManager;
27import android.bluetooth.BluetoothAdapter;
28import android.bluetooth.IBluetoothManager;
29import android.content.BroadcastReceiver;
30import android.content.Context;
31import android.content.DialogInterface;
32import android.content.Intent;
33import android.content.IntentFilter;
34import android.graphics.Color;
35import android.graphics.drawable.ColorDrawable;
36import android.media.AudioAttributes;
37import android.nfc.INfcAdapter;
38import android.nfc.NfcAdapter;
39import android.os.FileUtils;
40import android.os.Handler;
41import android.os.PowerManager;
42import android.os.RecoverySystem;
43import android.os.RemoteException;
44import android.os.ServiceManager;
45import android.os.SystemClock;
46import android.os.SystemProperties;
47import android.os.SystemVibrator;
48import android.os.UserHandle;
49import android.os.UserManager;
50import android.os.Vibrator;
51import android.os.storage.IStorageManager;
52import android.os.storage.IStorageShutdownObserver;
53import android.util.Log;
54import android.view.ViewGroup;
55import android.view.WindowManager;
56import android.widget.ProgressBar;
57import android.widget.TextView;
58
59import com.android.internal.telephony.ITelephony;
60import com.android.server.LocalServices;
61import com.android.server.pm.PackageManagerService;
62import com.android.server.statusbar.StatusBarManagerInternal;
63
64import java.io.File;
65import java.io.IOException;
66
67public final class ShutdownThread extends Thread {
68    // constants
69    private static final String TAG = "ShutdownThread";
70    private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
71    // maximum time we wait for the shutdown broadcast before going on.
72    private static final int MAX_BROADCAST_TIME = 10*1000;
73    private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
74    private static final int MAX_RADIO_WAIT_TIME = 12*1000;
75    private static final int MAX_UNCRYPT_WAIT_TIME = 15*60*1000;
76    // constants for progress bar. the values are roughly estimated based on timeout.
77    private static final int BROADCAST_STOP_PERCENT = 2;
78    private static final int ACTIVITY_MANAGER_STOP_PERCENT = 4;
79    private static final int PACKAGE_MANAGER_STOP_PERCENT = 6;
80    private static final int RADIO_STOP_PERCENT = 18;
81    private static final int MOUNT_SERVICE_STOP_PERCENT = 20;
82
83    // length of vibration before shutting down
84    private static final int SHUTDOWN_VIBRATE_MS = 500;
85
86    // state tracking
87    private static final Object sIsStartedGuard = new Object();
88    private static boolean sIsStarted = false;
89
90    private static boolean mReboot;
91    private static boolean mRebootSafeMode;
92    private static boolean mRebootHasProgressBar;
93    private static String mReason;
94
95    // Provides shutdown assurance in case the system_server is killed
96    public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested";
97
98    // Indicates whether we are rebooting into safe mode
99    public static final String REBOOT_SAFEMODE_PROPERTY = "persist.sys.safemode";
100    public static final String RO_SAFEMODE_PROPERTY = "ro.sys.safemode";
101
102    // static instance of this thread
103    private static final ShutdownThread sInstance = new ShutdownThread();
104
105    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
106            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
107            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
108            .build();
109
110    private final Object mActionDoneSync = new Object();
111    private boolean mActionDone;
112    private Context mContext;
113    private PowerManager mPowerManager;
114    private PowerManager.WakeLock mCpuWakeLock;
115    private PowerManager.WakeLock mScreenWakeLock;
116    private Handler mHandler;
117
118    private static AlertDialog sConfirmDialog;
119    private ProgressDialog mProgressDialog;
120
121    private ShutdownThread() {
122    }
123
124    /**
125     * Request a clean shutdown, waiting for subsystems to clean up their
126     * state etc.  Must be called from a Looper thread in which its UI
127     * is shown.
128     *
129     * @param context Context used to display the shutdown progress dialog. This must be a context
130     *                suitable for displaying UI (aka Themable).
131     * @param reason code to pass to android_reboot() (e.g. "userrequested"), or null.
132     * @param confirm true if user confirmation is needed before shutting down.
133     */
134    public static void shutdown(final Context context, String reason, boolean confirm) {
135        mReboot = false;
136        mRebootSafeMode = false;
137        mReason = reason;
138        shutdownInner(context, confirm);
139    }
140
141    private static void shutdownInner(final Context context, boolean confirm) {
142        // ShutdownThread is called from many places, so best to verify here that the context passed
143        // in is themed.
144        context.assertRuntimeOverlayThemable();
145
146        // ensure that only one thread is trying to power down.
147        // any additional calls are just returned
148        synchronized (sIsStartedGuard) {
149            if (sIsStarted) {
150                Log.d(TAG, "Request to shutdown already running, returning.");
151                return;
152            }
153        }
154
155        final int longPressBehavior = context.getResources().getInteger(
156                        com.android.internal.R.integer.config_longPressOnPowerBehavior);
157        final int resourceId = mRebootSafeMode
158                ? com.android.internal.R.string.reboot_safemode_confirm
159                : (longPressBehavior == 2
160                        ? com.android.internal.R.string.shutdown_confirm_question
161                        : com.android.internal.R.string.shutdown_confirm);
162
163        Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
164
165        if (confirm) {
166            final CloseDialogReceiver closer = new CloseDialogReceiver(context);
167            if (sConfirmDialog != null) {
168                sConfirmDialog.dismiss();
169            }
170            sConfirmDialog = new AlertDialog.Builder(context)
171                    .setTitle(mRebootSafeMode
172                            ? com.android.internal.R.string.reboot_safemode_title
173                            : com.android.internal.R.string.power_off)
174                    .setMessage(resourceId)
175                    .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
176                        public void onClick(DialogInterface dialog, int which) {
177                            beginShutdownSequence(context);
178                        }
179                    })
180                    .setNegativeButton(com.android.internal.R.string.no, null)
181                    .create();
182            closer.dialog = sConfirmDialog;
183            sConfirmDialog.setOnDismissListener(closer);
184            sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
185            sConfirmDialog.show();
186        } else {
187            beginShutdownSequence(context);
188        }
189    }
190
191    private static class CloseDialogReceiver extends BroadcastReceiver
192            implements DialogInterface.OnDismissListener {
193        private Context mContext;
194        public Dialog dialog;
195
196        CloseDialogReceiver(Context context) {
197            mContext = context;
198            IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
199            context.registerReceiver(this, filter);
200        }
201
202        @Override
203        public void onReceive(Context context, Intent intent) {
204            dialog.cancel();
205        }
206
207        public void onDismiss(DialogInterface unused) {
208            mContext.unregisterReceiver(this);
209        }
210    }
211
212    /**
213     * Request a clean shutdown, waiting for subsystems to clean up their
214     * state etc.  Must be called from a Looper thread in which its UI
215     * is shown.
216     *
217     * @param context Context used to display the shutdown progress dialog. This must be a context
218     *                suitable for displaying UI (aka Themable).
219     * @param reason code to pass to the kernel (e.g. "recovery"), or null.
220     * @param confirm true if user confirmation is needed before shutting down.
221     */
222    public static void reboot(final Context context, String reason, boolean confirm) {
223        mReboot = true;
224        mRebootSafeMode = false;
225        mRebootHasProgressBar = false;
226        mReason = reason;
227        shutdownInner(context, confirm);
228    }
229
230    /**
231     * Request a reboot into safe mode.  Must be called from a Looper thread in which its UI
232     * is shown.
233     *
234     * @param context Context used to display the shutdown progress dialog. This must be a context
235     *                suitable for displaying UI (aka Themable).
236     * @param confirm true if user confirmation is needed before shutting down.
237     */
238    public static void rebootSafeMode(final Context context, boolean confirm) {
239        UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
240        if (um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
241            return;
242        }
243
244        mReboot = true;
245        mRebootSafeMode = true;
246        mRebootHasProgressBar = false;
247        mReason = null;
248        shutdownInner(context, confirm);
249    }
250
251    private static ProgressDialog showShutdownDialog(Context context) {
252        // Throw up a system dialog to indicate the device is rebooting / shutting down.
253        ProgressDialog pd = new ProgressDialog(context);
254
255        // Path 1: Reboot to recovery for update
256        //   Condition: mReason startswith REBOOT_RECOVERY_UPDATE
257        //
258        //  Path 1a: uncrypt needed
259        //   Condition: if /cache/recovery/uncrypt_file exists but
260        //              /cache/recovery/block.map doesn't.
261        //   UI: determinate progress bar (mRebootHasProgressBar == True)
262        //
263        // * Path 1a is expected to be removed once the GmsCore shipped on
264        //   device always calls uncrypt prior to reboot.
265        //
266        //  Path 1b: uncrypt already done
267        //   UI: spinning circle only (no progress bar)
268        //
269        // Path 2: Reboot to recovery for factory reset
270        //   Condition: mReason == REBOOT_RECOVERY
271        //   UI: spinning circle only (no progress bar)
272        //
273        // Path 3: Regular reboot / shutdown
274        //   Condition: Otherwise
275        //   UI: spinning circle only (no progress bar)
276
277        // mReason could be "recovery-update" or "recovery-update,quiescent".
278        if (mReason != null && mReason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
279            // We need the progress bar if uncrypt will be invoked during the
280            // reboot, which might be time-consuming.
281            mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists()
282                    && !(RecoverySystem.BLOCK_MAP_FILE.exists());
283            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
284            if (mRebootHasProgressBar) {
285                pd.setMax(100);
286                pd.setProgress(0);
287                pd.setIndeterminate(false);
288                pd.setProgressNumberFormat(null);
289                pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
290                pd.setMessage(context.getText(
291                            com.android.internal.R.string.reboot_to_update_prepare));
292            } else {
293                if (showSysuiReboot()) {
294                    return null;
295                }
296                pd.setIndeterminate(true);
297                pd.setMessage(context.getText(
298                            com.android.internal.R.string.reboot_to_update_reboot));
299            }
300        } else if (mReason != null && mReason.equals(PowerManager.REBOOT_RECOVERY)) {
301            // Factory reset path. Set the dialog message accordingly.
302            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
303            pd.setMessage(context.getText(
304                    com.android.internal.R.string.reboot_to_reset_message));
305            pd.setIndeterminate(true);
306        } else {
307            if (showSysuiReboot()) {
308                return null;
309            }
310            pd.setTitle(context.getText(com.android.internal.R.string.power_off));
311            pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
312            pd.setIndeterminate(true);
313        }
314        pd.setCancelable(false);
315        pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
316
317        pd.show();
318        return pd;
319    }
320
321    private static boolean showSysuiReboot() {
322        Log.d(TAG, "Attempting to use SysUI shutdown UI");
323        try {
324            StatusBarManagerInternal service = LocalServices.getService(
325                    StatusBarManagerInternal.class);
326            if (service.showShutdownUi(mReboot, mReason)) {
327                // Sysui will handle shutdown UI.
328                Log.d(TAG, "SysUI handling shutdown UI");
329                return true;
330            }
331        } catch (Exception e) {
332            // If anything went wrong, ignore it and use fallback ui
333        }
334        Log.d(TAG, "SysUI is unavailable");
335        return false;
336    }
337
338    private static void beginShutdownSequence(Context context) {
339        synchronized (sIsStartedGuard) {
340            if (sIsStarted) {
341                Log.d(TAG, "Shutdown sequence already running, returning.");
342                return;
343            }
344            sIsStarted = true;
345        }
346
347        sInstance.mProgressDialog = showShutdownDialog(context);
348        sInstance.mContext = context;
349        sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
350
351        // make sure we never fall asleep again
352        sInstance.mCpuWakeLock = null;
353        try {
354            sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
355                    PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
356            sInstance.mCpuWakeLock.setReferenceCounted(false);
357            sInstance.mCpuWakeLock.acquire();
358        } catch (SecurityException e) {
359            Log.w(TAG, "No permission to acquire wake lock", e);
360            sInstance.mCpuWakeLock = null;
361        }
362
363        // also make sure the screen stays on for better user experience
364        sInstance.mScreenWakeLock = null;
365        if (sInstance.mPowerManager.isScreenOn()) {
366            try {
367                sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
368                        PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
369                sInstance.mScreenWakeLock.setReferenceCounted(false);
370                sInstance.mScreenWakeLock.acquire();
371            } catch (SecurityException e) {
372                Log.w(TAG, "No permission to acquire wake lock", e);
373                sInstance.mScreenWakeLock = null;
374            }
375        }
376
377        // start the thread that initiates shutdown
378        sInstance.mHandler = new Handler() {
379        };
380        sInstance.start();
381    }
382
383    void actionDone() {
384        synchronized (mActionDoneSync) {
385            mActionDone = true;
386            mActionDoneSync.notifyAll();
387        }
388    }
389
390    /**
391     * Makes sure we handle the shutdown gracefully.
392     * Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
393     */
394    public void run() {
395        BroadcastReceiver br = new BroadcastReceiver() {
396            @Override public void onReceive(Context context, Intent intent) {
397                // We don't allow apps to cancel this, so ignore the result.
398                actionDone();
399            }
400        };
401
402        /*
403         * Write a system property in case the system_server reboots before we
404         * get to the actual hardware restart. If that happens, we'll retry at
405         * the beginning of the SystemServer startup.
406         */
407        {
408            String reason = (mReboot ? "1" : "0") + (mReason != null ? mReason : "");
409            SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
410        }
411
412        /*
413         * If we are rebooting into safe mode, write a system property
414         * indicating so.
415         */
416        if (mRebootSafeMode) {
417            SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
418        }
419
420        Log.i(TAG, "Sending shutdown broadcast...");
421
422        // First send the high-level shut down broadcast.
423        mActionDone = false;
424        Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
425        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
426                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
427        mContext.sendOrderedBroadcastAsUser(intent,
428                UserHandle.ALL, null, br, mHandler, 0, null, null);
429
430        final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
431        synchronized (mActionDoneSync) {
432            while (!mActionDone) {
433                long delay = endTime - SystemClock.elapsedRealtime();
434                if (delay <= 0) {
435                    Log.w(TAG, "Shutdown broadcast timed out");
436                    break;
437                } else if (mRebootHasProgressBar) {
438                    int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 *
439                            BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME);
440                    sInstance.setRebootProgress(status, null);
441                }
442                try {
443                    mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC));
444                } catch (InterruptedException e) {
445                }
446            }
447        }
448        if (mRebootHasProgressBar) {
449            sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);
450        }
451
452        Log.i(TAG, "Shutting down activity manager...");
453
454        final IActivityManager am =
455                IActivityManager.Stub.asInterface(ServiceManager.checkService("activity"));
456        if (am != null) {
457            try {
458                am.shutdown(MAX_BROADCAST_TIME);
459            } catch (RemoteException e) {
460            }
461        }
462        if (mRebootHasProgressBar) {
463            sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
464        }
465
466        Log.i(TAG, "Shutting down package manager...");
467
468        final PackageManagerService pm = (PackageManagerService)
469            ServiceManager.getService("package");
470        if (pm != null) {
471            pm.shutdown();
472        }
473        if (mRebootHasProgressBar) {
474            sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
475        }
476
477        // Shutdown radios.
478        shutdownRadios(MAX_RADIO_WAIT_TIME);
479        if (mRebootHasProgressBar) {
480            sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
481        }
482
483        // Shutdown StorageManagerService to ensure media is in a safe state
484        IStorageShutdownObserver observer = new IStorageShutdownObserver.Stub() {
485            public void onShutDownComplete(int statusCode) throws RemoteException {
486                Log.w(TAG, "Result code " + statusCode + " from StorageManagerService.shutdown");
487                actionDone();
488            }
489        };
490
491        Log.i(TAG, "Shutting down StorageManagerService");
492
493        // Set initial variables and time out time.
494        mActionDone = false;
495        final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
496        synchronized (mActionDoneSync) {
497            try {
498                final IStorageManager storageManager = IStorageManager.Stub.asInterface(
499                        ServiceManager.checkService("mount"));
500                if (storageManager != null) {
501                    storageManager.shutdown(observer);
502                } else {
503                    Log.w(TAG, "StorageManagerService unavailable for shutdown");
504                }
505            } catch (Exception e) {
506                Log.e(TAG, "Exception during StorageManagerService shutdown", e);
507            }
508            while (!mActionDone) {
509                long delay = endShutTime - SystemClock.elapsedRealtime();
510                if (delay <= 0) {
511                    Log.w(TAG, "Shutdown wait timed out");
512                    break;
513                } else if (mRebootHasProgressBar) {
514                    int status = (int)((MAX_SHUTDOWN_WAIT_TIME - delay) * 1.0 *
515                            (MOUNT_SERVICE_STOP_PERCENT - RADIO_STOP_PERCENT) /
516                            MAX_SHUTDOWN_WAIT_TIME);
517                    status += RADIO_STOP_PERCENT;
518                    sInstance.setRebootProgress(status, null);
519                }
520                try {
521                    mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC));
522                } catch (InterruptedException e) {
523                }
524            }
525        }
526        if (mRebootHasProgressBar) {
527            sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);
528
529            // If it's to reboot to install an update and uncrypt hasn't been
530            // done yet, trigger it now.
531            uncrypt();
532        }
533
534        rebootOrShutdown(mContext, mReboot, mReason);
535    }
536
537    private void setRebootProgress(final int progress, final CharSequence message) {
538        mHandler.post(new Runnable() {
539            @Override
540            public void run() {
541                if (mProgressDialog != null) {
542                    mProgressDialog.setProgress(progress);
543                    if (message != null) {
544                        mProgressDialog.setMessage(message);
545                    }
546                }
547            }
548        });
549    }
550
551    private void shutdownRadios(final int timeout) {
552        // If a radio is wedged, disabling it may hang so we do this work in another thread,
553        // just in case.
554        final long endTime = SystemClock.elapsedRealtime() + timeout;
555        final boolean[] done = new boolean[1];
556        Thread t = new Thread() {
557            public void run() {
558                boolean nfcOff;
559                boolean bluetoothOff;
560                boolean radioOff;
561
562                final INfcAdapter nfc =
563                        INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc"));
564                final ITelephony phone =
565                        ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
566                final IBluetoothManager bluetooth =
567                        IBluetoothManager.Stub.asInterface(ServiceManager.checkService(
568                                BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE));
569
570                try {
571                    nfcOff = nfc == null ||
572                             nfc.getState() == NfcAdapter.STATE_OFF;
573                    if (!nfcOff) {
574                        Log.w(TAG, "Turning off NFC...");
575                        nfc.disable(false); // Don't persist new state
576                    }
577                } catch (RemoteException ex) {
578                Log.e(TAG, "RemoteException during NFC shutdown", ex);
579                    nfcOff = true;
580                }
581
582                try {
583                    bluetoothOff = bluetooth == null ||
584                            bluetooth.getState() == BluetoothAdapter.STATE_OFF;
585                    if (!bluetoothOff) {
586                        Log.w(TAG, "Disabling Bluetooth...");
587                        bluetooth.disable(mContext.getPackageName(), false);  // disable but don't persist new state
588                    }
589                } catch (RemoteException ex) {
590                    Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
591                    bluetoothOff = true;
592                }
593
594                try {
595                    radioOff = phone == null || !phone.needMobileRadioShutdown();
596                    if (!radioOff) {
597                        Log.w(TAG, "Turning off cellular radios...");
598                        phone.shutdownMobileRadios();
599                    }
600                } catch (RemoteException ex) {
601                    Log.e(TAG, "RemoteException during radio shutdown", ex);
602                    radioOff = true;
603                }
604
605                Log.i(TAG, "Waiting for NFC, Bluetooth and Radio...");
606
607                long delay = endTime - SystemClock.elapsedRealtime();
608                while (delay > 0) {
609                    if (mRebootHasProgressBar) {
610                        int status = (int)((timeout - delay) * 1.0 *
611                                (RADIO_STOP_PERCENT - PACKAGE_MANAGER_STOP_PERCENT) / timeout);
612                        status += PACKAGE_MANAGER_STOP_PERCENT;
613                        sInstance.setRebootProgress(status, null);
614                    }
615
616                    if (!bluetoothOff) {
617                        try {
618                            bluetoothOff = bluetooth.getState() == BluetoothAdapter.STATE_OFF;
619                        } catch (RemoteException ex) {
620                            Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
621                            bluetoothOff = true;
622                        }
623                        if (bluetoothOff) {
624                            Log.i(TAG, "Bluetooth turned off.");
625                        }
626                    }
627                    if (!radioOff) {
628                        try {
629                            radioOff = !phone.needMobileRadioShutdown();
630                        } catch (RemoteException ex) {
631                            Log.e(TAG, "RemoteException during radio shutdown", ex);
632                            radioOff = true;
633                        }
634                        if (radioOff) {
635                            Log.i(TAG, "Radio turned off.");
636                        }
637                    }
638                    if (!nfcOff) {
639                        try {
640                            nfcOff = nfc.getState() == NfcAdapter.STATE_OFF;
641                        } catch (RemoteException ex) {
642                            Log.e(TAG, "RemoteException during NFC shutdown", ex);
643                            nfcOff = true;
644                        }
645                        if (nfcOff) {
646                            Log.i(TAG, "NFC turned off.");
647                        }
648                    }
649
650                    if (radioOff && bluetoothOff && nfcOff) {
651                        Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete.");
652                        done[0] = true;
653                        break;
654                    }
655                    SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
656
657                    delay = endTime - SystemClock.elapsedRealtime();
658                }
659            }
660        };
661
662        t.start();
663        try {
664            t.join(timeout);
665        } catch (InterruptedException ex) {
666        }
667        if (!done[0]) {
668            Log.w(TAG, "Timed out waiting for NFC, Radio and Bluetooth shutdown.");
669        }
670    }
671
672    /**
673     * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
674     * or {@link #shutdown(Context, boolean)} instead.
675     *
676     * @param context Context used to vibrate or null without vibration
677     * @param reboot true to reboot or false to shutdown
678     * @param reason reason for reboot/shutdown
679     */
680    public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
681        if (reboot) {
682            Log.i(TAG, "Rebooting, reason: " + reason);
683            PowerManagerService.lowLevelReboot(reason);
684            Log.e(TAG, "Reboot failed, will attempt shutdown instead");
685            reason = null;
686        } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
687            // vibrate before shutting down
688            Vibrator vibrator = new SystemVibrator(context);
689            try {
690                vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
691            } catch (Exception e) {
692                // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
693                Log.w(TAG, "Failed to vibrate during shutdown.", e);
694            }
695
696            // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
697            try {
698                Thread.sleep(SHUTDOWN_VIBRATE_MS);
699            } catch (InterruptedException unused) {
700            }
701        }
702
703        // Shutdown power
704        Log.i(TAG, "Performing low-level shutdown...");
705        PowerManagerService.lowLevelShutdown(reason);
706    }
707
708    private void uncrypt() {
709        Log.i(TAG, "Calling uncrypt and monitoring the progress...");
710
711        final RecoverySystem.ProgressListener progressListener =
712                new RecoverySystem.ProgressListener() {
713            @Override
714            public void onProgress(int status) {
715                if (status >= 0 && status < 100) {
716                    // Scale down to [MOUNT_SERVICE_STOP_PERCENT, 100).
717                    status = (int)(status * (100.0 - MOUNT_SERVICE_STOP_PERCENT) / 100);
718                    status += MOUNT_SERVICE_STOP_PERCENT;
719                    CharSequence msg = mContext.getText(
720                            com.android.internal.R.string.reboot_to_update_package);
721                    sInstance.setRebootProgress(status, msg);
722                } else if (status == 100) {
723                    CharSequence msg = mContext.getText(
724                            com.android.internal.R.string.reboot_to_update_reboot);
725                    sInstance.setRebootProgress(status, msg);
726                } else {
727                    // Ignored
728                }
729            }
730        };
731
732        final boolean[] done = new boolean[1];
733        done[0] = false;
734        Thread t = new Thread() {
735            @Override
736            public void run() {
737                RecoverySystem rs = (RecoverySystem) mContext.getSystemService(
738                        Context.RECOVERY_SERVICE);
739                String filename = null;
740                try {
741                    filename = FileUtils.readTextFile(RecoverySystem.UNCRYPT_PACKAGE_FILE, 0, null);
742                    rs.processPackage(mContext, new File(filename), progressListener);
743                } catch (IOException e) {
744                    Log.e(TAG, "Error uncrypting file", e);
745                }
746                done[0] = true;
747            }
748        };
749        t.start();
750
751        try {
752            t.join(MAX_UNCRYPT_WAIT_TIME);
753        } catch (InterruptedException unused) {
754        }
755        if (!done[0]) {
756            Log.w(TAG, "Timed out waiting for uncrypt.");
757            final int uncryptTimeoutError = 100;
758            String timeoutMessage = String.format("uncrypt_time: %d\n" + "uncrypt_error: %d\n",
759                    MAX_UNCRYPT_WAIT_TIME / 1000, uncryptTimeoutError);
760            try {
761                FileUtils.stringToFile(RecoverySystem.UNCRYPT_STATUS_FILE, timeoutMessage);
762            } catch (IOException e) {
763                Log.e(TAG, "Failed to write timeout message to uncrypt status", e);
764            }
765        }
766    }
767}
768