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