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