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