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 
ShutdownThread()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      */
shutdown(final Context context, String reason, boolean confirm)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 
shutdownInner(final Context context, boolean confirm)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 
CloseDialogReceiver(Context context)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
onReceive(Context context, Intent intent)197         public void onReceive(Context context, Intent intent) {
198             dialog.cancel();
199         }
200 
onDismiss(DialogInterface unused)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      */
reboot(final Context context, String reason, boolean confirm)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      */
rebootSafeMode(final Context context, boolean confirm)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 
beginShutdownSequence(Context context)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 
actionDone()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      */
run()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 
setRebootProgress(final int progress, final CharSequence message)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 
shutdownRadios(final int timeout)514     private void shutdownRadios(final int timeout) {
515         // If a radio is wedged, disabling it may hang so we do this work in another thread,
516         // just in case.
517         final long endTime = SystemClock.elapsedRealtime() + timeout;
518         final boolean[] done = new boolean[1];
519         Thread t = new Thread() {
520             public void run() {
521                 boolean nfcOff;
522                 boolean bluetoothOff;
523                 boolean radioOff;
524 
525                 final INfcAdapter nfc =
526                         INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc"));
527                 final ITelephony phone =
528                         ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
529                 final IBluetoothManager bluetooth =
530                         IBluetoothManager.Stub.asInterface(ServiceManager.checkService(
531                                 BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE));
532 
533                 try {
534                     nfcOff = nfc == null ||
535                              nfc.getState() == NfcAdapter.STATE_OFF;
536                     if (!nfcOff) {
537                         Log.w(TAG, "Turning off NFC...");
538                         nfc.disable(false); // Don't persist new state
539                     }
540                 } catch (RemoteException ex) {
541                 Log.e(TAG, "RemoteException during NFC shutdown", ex);
542                     nfcOff = true;
543                 }
544 
545                 try {
546                     bluetoothOff = bluetooth == null || !bluetooth.isEnabled();
547                     if (!bluetoothOff) {
548                         Log.w(TAG, "Disabling Bluetooth...");
549                         bluetooth.disable(false);  // disable but don't persist new state
550                     }
551                 } catch (RemoteException ex) {
552                     Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
553                     bluetoothOff = true;
554                 }
555 
556                 try {
557                     radioOff = phone == null || !phone.needMobileRadioShutdown();
558                     if (!radioOff) {
559                         Log.w(TAG, "Turning off cellular radios...");
560                         phone.shutdownMobileRadios();
561                     }
562                 } catch (RemoteException ex) {
563                     Log.e(TAG, "RemoteException during radio shutdown", ex);
564                     radioOff = true;
565                 }
566 
567                 Log.i(TAG, "Waiting for NFC, Bluetooth and Radio...");
568 
569                 long delay = endTime - SystemClock.elapsedRealtime();
570                 while (delay > 0) {
571                     if (mRebootHasProgressBar) {
572                         int status = (int)((timeout - delay) * 1.0 *
573                                 (RADIO_STOP_PERCENT - PACKAGE_MANAGER_STOP_PERCENT) / timeout);
574                         status += PACKAGE_MANAGER_STOP_PERCENT;
575                         sInstance.setRebootProgress(status, null);
576                     }
577 
578                     if (!bluetoothOff) {
579                         try {
580                             bluetoothOff = !bluetooth.isEnabled();
581                         } catch (RemoteException ex) {
582                             Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
583                             bluetoothOff = true;
584                         }
585                         if (bluetoothOff) {
586                             Log.i(TAG, "Bluetooth turned off.");
587                         }
588                     }
589                     if (!radioOff) {
590                         try {
591                             radioOff = !phone.needMobileRadioShutdown();
592                         } catch (RemoteException ex) {
593                             Log.e(TAG, "RemoteException during radio shutdown", ex);
594                             radioOff = true;
595                         }
596                         if (radioOff) {
597                             Log.i(TAG, "Radio turned off.");
598                         }
599                     }
600                     if (!nfcOff) {
601                         try {
602                             nfcOff = nfc.getState() == NfcAdapter.STATE_OFF;
603                         } catch (RemoteException ex) {
604                             Log.e(TAG, "RemoteException during NFC shutdown", ex);
605                             nfcOff = true;
606                         }
607                         if (nfcOff) {
608                             Log.i(TAG, "NFC turned off.");
609                         }
610                     }
611 
612                     if (radioOff && bluetoothOff && nfcOff) {
613                         Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete.");
614                         done[0] = true;
615                         break;
616                     }
617                     SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
618 
619                     delay = endTime - SystemClock.elapsedRealtime();
620                 }
621             }
622         };
623 
624         t.start();
625         try {
626             t.join(timeout);
627         } catch (InterruptedException ex) {
628         }
629         if (!done[0]) {
630             Log.w(TAG, "Timed out waiting for NFC, Radio and Bluetooth shutdown.");
631         }
632     }
633 
634     /**
635      * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
636      * or {@link #shutdown(Context, boolean)} instead.
637      *
638      * @param context Context used to vibrate or null without vibration
639      * @param reboot true to reboot or false to shutdown
640      * @param reason reason for reboot/shutdown
641      */
rebootOrShutdown(final Context context, boolean reboot, String reason)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 
uncrypt()670     private void uncrypt() {
671         Log.i(TAG, "Calling uncrypt and monitoring the progress...");
672 
673         final RecoverySystem.ProgressListener progressListener =
674                 new RecoverySystem.ProgressListener() {
675             @Override
676             public void onProgress(int status) {
677                 if (status >= 0 && status < 100) {
678                     // Scale down to [MOUNT_SERVICE_STOP_PERCENT, 100).
679                     status = (int)(status * (100.0 - MOUNT_SERVICE_STOP_PERCENT) / 100);
680                     status += MOUNT_SERVICE_STOP_PERCENT;
681                     CharSequence msg = mContext.getText(
682                             com.android.internal.R.string.reboot_to_update_package);
683                     sInstance.setRebootProgress(status, msg);
684                 } else if (status == 100) {
685                     CharSequence msg = mContext.getText(
686                             com.android.internal.R.string.reboot_to_update_reboot);
687                     sInstance.setRebootProgress(status, msg);
688                 } else {
689                     // Ignored
690                 }
691             }
692         };
693 
694         final boolean[] done = new boolean[1];
695         done[0] = false;
696         Thread t = new Thread() {
697             @Override
698             public void run() {
699                 RecoverySystem rs = (RecoverySystem) mContext.getSystemService(
700                         Context.RECOVERY_SERVICE);
701                 String filename = null;
702                 try {
703                     filename = FileUtils.readTextFile(RecoverySystem.UNCRYPT_PACKAGE_FILE, 0, null);
704                     rs.processPackage(mContext, new File(filename), progressListener);
705                 } catch (IOException e) {
706                     Log.e(TAG, "Error uncrypting file", e);
707                 }
708                 done[0] = true;
709             }
710         };
711         t.start();
712 
713         try {
714             t.join(MAX_UNCRYPT_WAIT_TIME);
715         } catch (InterruptedException unused) {
716         }
717         if (!done[0]) {
718             Log.w(TAG, "Timed out waiting for uncrypt.");
719         }
720     }
721 }
722