ShutdownThread.java revision 2250d56a0b47b93016018340c8f4040325aa5611
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.ProgressDialog;
24import android.bluetooth.BluetoothAdapter;
25import android.bluetooth.IBluetoothManager;
26import android.media.AudioAttributes;
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.FileUtils;
35import android.os.Handler;
36import android.os.PowerManager;
37import android.os.RecoverySystem;
38import android.os.RemoteException;
39import android.os.ServiceManager;
40import android.os.SystemClock;
41import android.os.SystemProperties;
42import android.os.UserHandle;
43import android.os.UserManager;
44import android.os.Vibrator;
45import android.os.SystemVibrator;
46import android.os.storage.IStorageShutdownObserver;
47import android.os.storage.IStorageManager;
48import android.system.ErrnoException;
49import android.system.Os;
50
51import com.android.internal.telephony.ITelephony;
52import com.android.server.pm.PackageManagerService;
53
54import android.util.Log;
55import android.view.WindowManager;
56
57import java.io.BufferedReader;
58import java.io.File;
59import java.io.FileReader;
60import java.io.IOException;
61
62public final class ShutdownThread extends Thread {
63    // constants
64    private static final String TAG = "ShutdownThread";
65    private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
66    // maximum time we wait for the shutdown broadcast before going on.
67    private static final int MAX_BROADCAST_TIME = 10*1000;
68    private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
69    private static final int MAX_RADIO_WAIT_TIME = 12*1000;
70    private static final int MAX_UNCRYPT_WAIT_TIME = 15*60*1000;
71    // constants for progress bar. the values are roughly estimated based on timeout.
72    private static final int BROADCAST_STOP_PERCENT = 2;
73    private static final int ACTIVITY_MANAGER_STOP_PERCENT = 4;
74    private static final int PACKAGE_MANAGER_STOP_PERCENT = 6;
75    private static final int RADIO_STOP_PERCENT = 18;
76    private static final int MOUNT_SERVICE_STOP_PERCENT = 20;
77
78    // length of vibration before shutting down
79    private static final int SHUTDOWN_VIBRATE_MS = 500;
80
81    // state tracking
82    private static Object sIsStartedGuard = new Object();
83    private static boolean sIsStarted = false;
84
85    private static boolean mReboot;
86    private static boolean mRebootSafeMode;
87    private static boolean mRebootHasProgressBar;
88    private static String mReason;
89
90    // Provides shutdown assurance in case the system_server is killed
91    public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested";
92
93    // Indicates whether we are rebooting into safe mode
94    public static final String REBOOT_SAFEMODE_PROPERTY = "persist.sys.safemode";
95    public static final String RO_SAFEMODE_PROPERTY = "ro.sys.safemode";
96
97    // Indicates whether we should stay in safe mode until ro.build.date.utc is newer than this
98    public static final String AUDIT_SAFEMODE_PROPERTY = "persist.sys.audit_safemode";
99
100    // static instance of this thread
101    private static final ShutdownThread sInstance = new ShutdownThread();
102
103    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
104            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
105            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
106            .build();
107
108    private final Object mActionDoneSync = new Object();
109    private boolean mActionDone;
110    private Context mContext;
111    private PowerManager mPowerManager;
112    private PowerManager.WakeLock mCpuWakeLock;
113    private PowerManager.WakeLock mScreenWakeLock;
114    private Handler mHandler;
115
116    private static AlertDialog sConfirmDialog;
117    private ProgressDialog mProgressDialog;
118
119    private ShutdownThread() {
120    }
121
122    /**
123     * Request a clean shutdown, waiting for subsystems to clean up their
124     * state etc.  Must be called from a Looper thread in which its UI
125     * is shown.
126     *
127     * @param context Context used to display the shutdown progress dialog.
128     * @param reason code to pass to android_reboot() (e.g. "userrequested"), or null.
129     * @param confirm true if user confirmation is needed before shutting down.
130     */
131    public static void shutdown(final Context context, String reason, boolean confirm) {
132        mReboot = false;
133        mRebootSafeMode = false;
134        mReason = reason;
135        shutdownInner(context, confirm);
136    }
137
138    static void shutdownInner(final Context context, boolean confirm) {
139        // ensure that only one thread is trying to power down.
140        // any additional calls are just returned
141        synchronized (sIsStartedGuard) {
142            if (sIsStarted) {
143                Log.d(TAG, "Request to shutdown already running, returning.");
144                return;
145            }
146        }
147
148        final int longPressBehavior = context.getResources().getInteger(
149                        com.android.internal.R.integer.config_longPressOnPowerBehavior);
150        final int resourceId = mRebootSafeMode
151                ? com.android.internal.R.string.reboot_safemode_confirm
152                : (longPressBehavior == 2
153                        ? com.android.internal.R.string.shutdown_confirm_question
154                        : com.android.internal.R.string.shutdown_confirm);
155
156        Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
157
158        if (confirm) {
159            final CloseDialogReceiver closer = new CloseDialogReceiver(context);
160            if (sConfirmDialog != null) {
161                sConfirmDialog.dismiss();
162            }
163            sConfirmDialog = new AlertDialog.Builder(context)
164                    .setTitle(mRebootSafeMode
165                            ? com.android.internal.R.string.reboot_safemode_title
166                            : com.android.internal.R.string.power_off)
167                    .setMessage(resourceId)
168                    .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
169                        public void onClick(DialogInterface dialog, int which) {
170                            beginShutdownSequence(context);
171                        }
172                    })
173                    .setNegativeButton(com.android.internal.R.string.no, null)
174                    .create();
175            closer.dialog = sConfirmDialog;
176            sConfirmDialog.setOnDismissListener(closer);
177            sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
178            sConfirmDialog.show();
179        } else {
180            beginShutdownSequence(context);
181        }
182    }
183
184    private static class CloseDialogReceiver extends BroadcastReceiver
185            implements DialogInterface.OnDismissListener {
186        private Context mContext;
187        public Dialog dialog;
188
189        CloseDialogReceiver(Context context) {
190            mContext = context;
191            IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
192            context.registerReceiver(this, filter);
193        }
194
195        @Override
196        public void onReceive(Context context, Intent intent) {
197            dialog.cancel();
198        }
199
200        public void onDismiss(DialogInterface unused) {
201            mContext.unregisterReceiver(this);
202        }
203    }
204
205    /**
206     * Request a clean shutdown, waiting for subsystems to clean up their
207     * state etc.  Must be called from a Looper thread in which its UI
208     * is shown.
209     *
210     * @param context Context used to display the shutdown progress dialog.
211     * @param reason code to pass to the kernel (e.g. "recovery"), or null.
212     * @param confirm true if user confirmation is needed before shutting down.
213     */
214    public static void reboot(final Context context, String reason, boolean confirm) {
215        mReboot = true;
216        mRebootSafeMode = false;
217        mRebootHasProgressBar = false;
218        mReason = reason;
219        shutdownInner(context, confirm);
220    }
221
222    /**
223     * Request a reboot into safe mode.  Must be called from a Looper thread in which its UI
224     * is shown.
225     *
226     * @param context Context used to display the shutdown progress dialog.
227     * @param confirm true if user confirmation is needed before shutting down.
228     */
229    public static void rebootSafeMode(final Context context, boolean confirm) {
230        UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
231        if (um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
232            return;
233        }
234
235        mReboot = true;
236        mRebootSafeMode = true;
237        mRebootHasProgressBar = false;
238        mReason = null;
239        shutdownInner(context, confirm);
240    }
241
242    private static void beginShutdownSequence(Context context) {
243        synchronized (sIsStartedGuard) {
244            if (sIsStarted) {
245                Log.d(TAG, "Shutdown sequence already running, returning.");
246                return;
247            }
248            sIsStarted = true;
249        }
250
251        // Throw up a system dialog to indicate the device is rebooting / shutting down.
252        ProgressDialog pd = new ProgressDialog(context);
253
254        // Path 1: Reboot to recovery for update
255        //   Condition: mReason == REBOOT_RECOVERY_UPDATE
256        //
257        //  Path 1a: uncrypt needed
258        //   Condition: if /cache/recovery/uncrypt_file exists but
259        //              /cache/recovery/block.map doesn't.
260        //   UI: determinate progress bar (mRebootHasProgressBar == True)
261        //
262        // * Path 1a is expected to be removed once the GmsCore shipped on
263        //   device always calls uncrypt prior to reboot.
264        //
265        //  Path 1b: uncrypt already done
266        //   UI: spinning circle only (no progress bar)
267        //
268        // Path 2: Reboot to recovery for factory reset
269        //   Condition: mReason == REBOOT_RECOVERY
270        //   UI: spinning circle only (no progress bar)
271        //
272        // Path 3: Regular reboot / shutdown
273        //   Condition: Otherwise
274        //   UI: spinning circle only (no progress bar)
275        if (PowerManager.REBOOT_RECOVERY_UPDATE.equals(mReason)) {
276            // We need the progress bar if uncrypt will be invoked during the
277            // reboot, which might be time-consuming.
278            mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists()
279                    && !(RecoverySystem.BLOCK_MAP_FILE.exists());
280            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
281            if (mRebootHasProgressBar) {
282                pd.setMax(100);
283                pd.setProgress(0);
284                pd.setIndeterminate(false);
285                pd.setProgressNumberFormat(null);
286                pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
287                pd.setMessage(context.getText(
288                            com.android.internal.R.string.reboot_to_update_prepare));
289            } else {
290                pd.setIndeterminate(true);
291                pd.setMessage(context.getText(
292                            com.android.internal.R.string.reboot_to_update_reboot));
293            }
294        } else if (PowerManager.REBOOT_RECOVERY.equals(mReason)) {
295            // Factory reset path. Set the dialog message accordingly.
296            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
297            pd.setMessage(context.getText(
298                        com.android.internal.R.string.reboot_to_reset_message));
299            pd.setIndeterminate(true);
300        } else {
301            pd.setTitle(context.getText(com.android.internal.R.string.power_off));
302            pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
303            pd.setIndeterminate(true);
304        }
305        pd.setCancelable(false);
306        pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
307
308        pd.show();
309
310        sInstance.mProgressDialog = pd;
311        sInstance.mContext = context;
312        sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
313
314        // make sure we never fall asleep again
315        sInstance.mCpuWakeLock = null;
316        try {
317            sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
318                    PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
319            sInstance.mCpuWakeLock.setReferenceCounted(false);
320            sInstance.mCpuWakeLock.acquire();
321        } catch (SecurityException e) {
322            Log.w(TAG, "No permission to acquire wake lock", e);
323            sInstance.mCpuWakeLock = null;
324        }
325
326        // also make sure the screen stays on for better user experience
327        sInstance.mScreenWakeLock = null;
328        if (sInstance.mPowerManager.isScreenOn()) {
329            try {
330                sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
331                        PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
332                sInstance.mScreenWakeLock.setReferenceCounted(false);
333                sInstance.mScreenWakeLock.acquire();
334            } catch (SecurityException e) {
335                Log.w(TAG, "No permission to acquire wake lock", e);
336                sInstance.mScreenWakeLock = null;
337            }
338        }
339
340        // start the thread that initiates shutdown
341        sInstance.mHandler = new Handler() {
342        };
343        sInstance.start();
344    }
345
346    void actionDone() {
347        synchronized (mActionDoneSync) {
348            mActionDone = true;
349            mActionDoneSync.notifyAll();
350        }
351    }
352
353    /**
354     * Makes sure we handle the shutdown gracefully.
355     * Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
356     */
357    public void run() {
358        BroadcastReceiver br = new BroadcastReceiver() {
359            @Override public void onReceive(Context context, Intent intent) {
360                // We don't allow apps to cancel this, so ignore the result.
361                actionDone();
362            }
363        };
364
365        /*
366         * Write a system property in case the system_server reboots before we
367         * get to the actual hardware restart. If that happens, we'll retry at
368         * the beginning of the SystemServer startup.
369         */
370        {
371            String reason = (mReboot ? "1" : "0") + (mReason != null ? mReason : "");
372            SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
373        }
374
375        /*
376         * If we are rebooting into safe mode, write a system property
377         * indicating so.
378         */
379        if (mRebootSafeMode) {
380            SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
381        }
382
383        Log.i(TAG, "Sending shutdown broadcast...");
384
385        // First send the high-level shut down broadcast.
386        mActionDone = false;
387        Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
388        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
389        mContext.sendOrderedBroadcastAsUser(intent,
390                UserHandle.ALL, null, br, mHandler, 0, null, null);
391
392        final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
393        synchronized (mActionDoneSync) {
394            while (!mActionDone) {
395                long delay = endTime - SystemClock.elapsedRealtime();
396                if (delay <= 0) {
397                    Log.w(TAG, "Shutdown broadcast timed out");
398                    break;
399                } else if (mRebootHasProgressBar) {
400                    int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 *
401                            BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME);
402                    sInstance.setRebootProgress(status, null);
403                }
404                try {
405                    mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC));
406                } catch (InterruptedException e) {
407                }
408            }
409        }
410        if (mRebootHasProgressBar) {
411            sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);
412        }
413
414        Log.i(TAG, "Shutting down activity manager...");
415
416        final IActivityManager am =
417                IActivityManager.Stub.asInterface(ServiceManager.checkService("activity"));
418        if (am != null) {
419            try {
420                am.shutdown(MAX_BROADCAST_TIME);
421            } catch (RemoteException e) {
422            }
423        }
424        if (mRebootHasProgressBar) {
425            sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
426        }
427
428        Log.i(TAG, "Shutting down package manager...");
429
430        final PackageManagerService pm = (PackageManagerService)
431            ServiceManager.getService("package");
432        if (pm != null) {
433            pm.shutdown();
434        }
435        if (mRebootHasProgressBar) {
436            sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
437        }
438
439        // Shutdown radios.
440        shutdownRadios(MAX_RADIO_WAIT_TIME);
441        if (mRebootHasProgressBar) {
442            sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
443        }
444
445        // Shutdown StorageManagerService to ensure media is in a safe state
446        IStorageShutdownObserver observer = new IStorageShutdownObserver.Stub() {
447            public void onShutDownComplete(int statusCode) throws RemoteException {
448                Log.w(TAG, "Result code " + statusCode + " from StorageManagerService.shutdown");
449                actionDone();
450            }
451        };
452
453        Log.i(TAG, "Shutting down StorageManagerService");
454
455        // Set initial variables and time out time.
456        mActionDone = false;
457        final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
458        synchronized (mActionDoneSync) {
459            try {
460                final IStorageManager storageManager = IStorageManager.Stub.asInterface(
461                        ServiceManager.checkService("mount"));
462                if (storageManager != null) {
463                    storageManager.shutdown(observer);
464                } else {
465                    Log.w(TAG, "StorageManagerService unavailable for shutdown");
466                }
467            } catch (Exception e) {
468                Log.e(TAG, "Exception during StorageManagerService shutdown", e);
469            }
470            while (!mActionDone) {
471                long delay = endShutTime - SystemClock.elapsedRealtime();
472                if (delay <= 0) {
473                    Log.w(TAG, "Shutdown wait timed out");
474                    break;
475                } else if (mRebootHasProgressBar) {
476                    int status = (int)((MAX_SHUTDOWN_WAIT_TIME - delay) * 1.0 *
477                            (MOUNT_SERVICE_STOP_PERCENT - RADIO_STOP_PERCENT) /
478                            MAX_SHUTDOWN_WAIT_TIME);
479                    status += RADIO_STOP_PERCENT;
480                    sInstance.setRebootProgress(status, null);
481                }
482                try {
483                    mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC));
484                } catch (InterruptedException e) {
485                }
486            }
487        }
488        if (mRebootHasProgressBar) {
489            sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);
490
491            // If it's to reboot to install an update and uncrypt hasn't been
492            // done yet, trigger it now.
493            uncrypt();
494        }
495
496        rebootOrShutdown(mContext, mReboot, mReason);
497    }
498
499    private void setRebootProgress(final int progress, final CharSequence message) {
500        mHandler.post(new Runnable() {
501            @Override
502            public void run() {
503                if (mProgressDialog != null) {
504                    mProgressDialog.setProgress(progress);
505                    if (message != null) {
506                        mProgressDialog.setMessage(message);
507                    }
508                }
509            }
510        });
511    }
512
513    private void shutdownRadios(final int timeout) {
514        // If a radio is wedged, disabling it may hang so we do this work in another thread,
515        // just in case.
516        final long endTime = SystemClock.elapsedRealtime() + timeout;
517        final boolean[] done = new boolean[1];
518        Thread t = new Thread() {
519            public void run() {
520                boolean nfcOff;
521                boolean bluetoothOff;
522                boolean radioOff;
523
524                final INfcAdapter nfc =
525                        INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc"));
526                final ITelephony phone =
527                        ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
528                final IBluetoothManager bluetooth =
529                        IBluetoothManager.Stub.asInterface(ServiceManager.checkService(
530                                BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE));
531
532                try {
533                    nfcOff = nfc == null ||
534                             nfc.getState() == NfcAdapter.STATE_OFF;
535                    if (!nfcOff) {
536                        Log.w(TAG, "Turning off NFC...");
537                        nfc.disable(false); // Don't persist new state
538                    }
539                } catch (RemoteException ex) {
540                Log.e(TAG, "RemoteException during NFC shutdown", ex);
541                    nfcOff = true;
542                }
543
544                try {
545                    bluetoothOff = bluetooth == null ||
546                            bluetooth.getState() == BluetoothAdapter.STATE_OFF;
547                    if (!bluetoothOff) {
548                        Log.w(TAG, "Disabling Bluetooth...");
549                        bluetooth.disable(mContext.getPackageName(), 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.getState() == BluetoothAdapter.STATE_OFF;
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            final int uncryptTimeoutError = 100;
720            String timeoutMessage = String.format("uncrypt_time: %d\n" + "uncrypt_error: %d\n",
721                    MAX_UNCRYPT_WAIT_TIME / 1000, uncryptTimeoutError);
722            try {
723                FileUtils.stringToFile(RecoverySystem.UNCRYPT_STATUS_FILE, timeoutMessage);
724            } catch (IOException e) {
725                Log.e(TAG, "Failed to write timeout message to uncrypt status", e);
726            }
727        }
728    }
729}
730