OSDN Git Service

resolve merge conflicts of 56a2b529373b to nyc-dev am: d7dcc3e227
[android-x86/frameworks-base.git] / services / core / java / com / android / server / power / ShutdownThread.java
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
18 package com.android.server.power;
19
20 import android.app.ActivityManagerNative;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.app.IActivityManager;
24 import android.app.ProgressDialog;
25 import android.bluetooth.BluetoothAdapter;
26 import android.bluetooth.IBluetoothManager;
27 import android.media.AudioAttributes;
28 import android.nfc.NfcAdapter;
29 import android.nfc.INfcAdapter;
30 import android.content.BroadcastReceiver;
31 import android.content.Context;
32 import android.content.DialogInterface;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.os.FileUtils;
36 import android.os.Handler;
37 import android.os.PowerManager;
38 import android.os.RecoverySystem;
39 import android.os.RemoteException;
40 import android.os.ServiceManager;
41 import android.os.SystemClock;
42 import android.os.SystemProperties;
43 import android.os.UserHandle;
44 import android.os.UserManager;
45 import android.os.Vibrator;
46 import android.os.SystemVibrator;
47 import android.os.storage.IMountService;
48 import android.os.storage.IMountShutdownObserver;
49 import android.system.ErrnoException;
50 import android.system.Os;
51
52 import com.android.internal.telephony.ITelephony;
53 import com.android.server.pm.PackageManagerService;
54
55 import android.util.Log;
56 import android.view.WindowManager;
57
58 import java.io.BufferedReader;
59 import java.io.File;
60 import java.io.FileReader;
61 import java.io.IOException;
62
63 public 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 ||
547                             bluetooth.getState() == BluetoothAdapter.STATE_OFF;
548                     if (!bluetoothOff) {
549                         Log.w(TAG, "Disabling Bluetooth...");
550                         bluetooth.disable(false);  // disable but don't persist new state
551                     }
552                 } catch (RemoteException ex) {
553                     Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
554                     bluetoothOff = true;
555                 }
556
557                 try {
558                     radioOff = phone == null || !phone.needMobileRadioShutdown();
559                     if (!radioOff) {
560                         Log.w(TAG, "Turning off cellular radios...");
561                         phone.shutdownMobileRadios();
562                     }
563                 } catch (RemoteException ex) {
564                     Log.e(TAG, "RemoteException during radio shutdown", ex);
565                     radioOff = true;
566                 }
567
568                 Log.i(TAG, "Waiting for NFC, Bluetooth and Radio...");
569
570                 long delay = endTime - SystemClock.elapsedRealtime();
571                 while (delay > 0) {
572                     if (mRebootHasProgressBar) {
573                         int status = (int)((timeout - delay) * 1.0 *
574                                 (RADIO_STOP_PERCENT - PACKAGE_MANAGER_STOP_PERCENT) / timeout);
575                         status += PACKAGE_MANAGER_STOP_PERCENT;
576                         sInstance.setRebootProgress(status, null);
577                     }
578
579                     if (!bluetoothOff) {
580                         try {
581                             bluetoothOff = bluetooth.getState() == BluetoothAdapter.STATE_OFF;
582                         } catch (RemoteException ex) {
583                             Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
584                             bluetoothOff = true;
585                         }
586                         if (bluetoothOff) {
587                             Log.i(TAG, "Bluetooth turned off.");
588                         }
589                     }
590                     if (!radioOff) {
591                         try {
592                             radioOff = !phone.needMobileRadioShutdown();
593                         } catch (RemoteException ex) {
594                             Log.e(TAG, "RemoteException during radio shutdown", ex);
595                             radioOff = true;
596                         }
597                         if (radioOff) {
598                             Log.i(TAG, "Radio turned off.");
599                         }
600                     }
601                     if (!nfcOff) {
602                         try {
603                             nfcOff = nfc.getState() == NfcAdapter.STATE_OFF;
604                         } catch (RemoteException ex) {
605                             Log.e(TAG, "RemoteException during NFC shutdown", ex);
606                             nfcOff = true;
607                         }
608                         if (nfcOff) {
609                             Log.i(TAG, "NFC turned off.");
610                         }
611                     }
612
613                     if (radioOff && bluetoothOff && nfcOff) {
614                         Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete.");
615                         done[0] = true;
616                         break;
617                     }
618                     SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
619
620                     delay = endTime - SystemClock.elapsedRealtime();
621                 }
622             }
623         };
624
625         t.start();
626         try {
627             t.join(timeout);
628         } catch (InterruptedException ex) {
629         }
630         if (!done[0]) {
631             Log.w(TAG, "Timed out waiting for NFC, Radio and Bluetooth shutdown.");
632         }
633     }
634
635     /**
636      * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
637      * or {@link #shutdown(Context, boolean)} instead.
638      *
639      * @param context Context used to vibrate or null without vibration
640      * @param reboot true to reboot or false to shutdown
641      * @param reason reason for reboot/shutdown
642      */
643     public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
644         if (reboot) {
645             Log.i(TAG, "Rebooting, reason: " + reason);
646             PowerManagerService.lowLevelReboot(reason);
647             Log.e(TAG, "Reboot failed, will attempt shutdown instead");
648             reason = null;
649         } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
650             // vibrate before shutting down
651             Vibrator vibrator = new SystemVibrator(context);
652             try {
653                 vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
654             } catch (Exception e) {
655                 // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
656                 Log.w(TAG, "Failed to vibrate during shutdown.", e);
657             }
658
659             // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
660             try {
661                 Thread.sleep(SHUTDOWN_VIBRATE_MS);
662             } catch (InterruptedException unused) {
663             }
664         }
665
666         // Shutdown power
667         Log.i(TAG, "Performing low-level shutdown...");
668         PowerManagerService.lowLevelShutdown(reason);
669     }
670
671     private void uncrypt() {
672         Log.i(TAG, "Calling uncrypt and monitoring the progress...");
673
674         final RecoverySystem.ProgressListener progressListener =
675                 new RecoverySystem.ProgressListener() {
676             @Override
677             public void onProgress(int status) {
678                 if (status >= 0 && status < 100) {
679                     // Scale down to [MOUNT_SERVICE_STOP_PERCENT, 100).
680                     status = (int)(status * (100.0 - MOUNT_SERVICE_STOP_PERCENT) / 100);
681                     status += MOUNT_SERVICE_STOP_PERCENT;
682                     CharSequence msg = mContext.getText(
683                             com.android.internal.R.string.reboot_to_update_package);
684                     sInstance.setRebootProgress(status, msg);
685                 } else if (status == 100) {
686                     CharSequence msg = mContext.getText(
687                             com.android.internal.R.string.reboot_to_update_reboot);
688                     sInstance.setRebootProgress(status, msg);
689                 } else {
690                     // Ignored
691                 }
692             }
693         };
694
695         final boolean[] done = new boolean[1];
696         done[0] = false;
697         Thread t = new Thread() {
698             @Override
699             public void run() {
700                 RecoverySystem rs = (RecoverySystem) mContext.getSystemService(
701                         Context.RECOVERY_SERVICE);
702                 String filename = null;
703                 try {
704                     filename = FileUtils.readTextFile(RecoverySystem.UNCRYPT_PACKAGE_FILE, 0, null);
705                     rs.processPackage(mContext, new File(filename), progressListener);
706                 } catch (IOException e) {
707                     Log.e(TAG, "Error uncrypting file", e);
708                 }
709                 done[0] = true;
710             }
711         };
712         t.start();
713
714         try {
715             t.join(MAX_UNCRYPT_WAIT_TIME);
716         } catch (InterruptedException unused) {
717         }
718         if (!done[0]) {
719             Log.w(TAG, "Timed out waiting for uncrypt.");
720             final int uncryptTimeoutError = 100;
721             String timeoutMessage = String.format("uncrypt_time: %d\n" + "uncrypt_error: %d\n",
722                     MAX_UNCRYPT_WAIT_TIME / 1000, uncryptTimeoutError);
723             try {
724                 FileUtils.stringToFile(RecoverySystem.UNCRYPT_STATUS_FILE, timeoutMessage);
725             } catch (IOException e) {
726                 Log.e(TAG, "Failed to write timeout message to uncrypt status", e);
727             }
728         }
729     }
730 }