1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18package com.android.server.power;
19
20import android.app.AlertDialog;
21import android.app.Dialog;
22import android.app.IActivityManager;
23import android.app.ProgressDialog;
24import android.app.admin.SecurityLog;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.DialogInterface;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.media.AudioAttributes;
31import android.os.FileUtils;
32import android.os.Handler;
33import android.os.PowerManager;
34import android.os.RecoverySystem;
35import android.os.RemoteException;
36import android.os.ServiceManager;
37import android.os.SystemClock;
38import android.os.SystemProperties;
39import android.os.SystemVibrator;
40import android.os.Trace;
41import android.os.UserHandle;
42import android.os.UserManager;
43import android.os.Vibrator;
44import android.util.ArrayMap;
45import android.util.Log;
46import android.util.TimingsTraceLog;
47import android.view.WindowManager;
48
49import com.android.internal.telephony.ITelephony;
50import com.android.server.RescueParty;
51import com.android.server.LocalServices;
52import com.android.server.pm.PackageManagerService;
53import com.android.server.statusbar.StatusBarManagerInternal;
54
55import java.io.File;
56import java.io.FileOutputStream;
57import java.io.IOException;
58import java.nio.charset.StandardCharsets;
59
60public final class ShutdownThread extends Thread {
61    // constants
62    private static final String TAG = "ShutdownThread";
63    private static final int ACTION_DONE_POLL_WAIT_MS = 500;
64    private static final int RADIOS_STATE_POLL_SLEEP_MS = 100;
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 final Object sIsStartedGuard = new Object();
82    private static boolean sIsStarted = false;
83
84    private static boolean mReboot;
85    private static boolean mRebootSafeMode;
86    private static boolean mRebootHasProgressBar;
87    private static String mReason;
88
89    // Provides shutdown assurance in case the system_server is killed
90    public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested";
91
92    // Indicates whether we are rebooting into safe mode
93    public static final String REBOOT_SAFEMODE_PROPERTY = "persist.sys.safemode";
94    public static final String RO_SAFEMODE_PROPERTY = "ro.sys.safemode";
95
96    // static instance of this thread
97    private static final ShutdownThread sInstance = new ShutdownThread();
98
99    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
100            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
101            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
102            .build();
103
104    // Metrics that will be reported to tron after reboot
105    private static final ArrayMap<String, Long> TRON_METRICS = new ArrayMap<>();
106
107    // File to use for save metrics
108    private static final String METRICS_FILE_BASENAME = "/data/system/shutdown-metrics";
109
110    // Metrics names to be persisted in shutdown-metrics file
111    private static String METRIC_SYSTEM_SERVER = "shutdown_system_server";
112    private static String METRIC_SEND_BROADCAST = "shutdown_send_shutdown_broadcast";
113    private static String METRIC_AM = "shutdown_activity_manager";
114    private static String METRIC_PM = "shutdown_package_manager";
115    private static String METRIC_RADIOS = "shutdown_radios";
116    private static String METRIC_RADIO = "shutdown_radio";
117    private static String METRIC_SHUTDOWN_TIME_START = "begin_shutdown";
118
119    private final Object mActionDoneSync = new Object();
120    private boolean mActionDone;
121    private Context mContext;
122    private PowerManager mPowerManager;
123    private PowerManager.WakeLock mCpuWakeLock;
124    private PowerManager.WakeLock mScreenWakeLock;
125    private Handler mHandler;
126
127    private static AlertDialog sConfirmDialog;
128    private ProgressDialog mProgressDialog;
129
130    private ShutdownThread() {
131    }
132
133    /**
134     * Request a clean shutdown, waiting for subsystems to clean up their
135     * state etc.  Must be called from a Looper thread in which its UI
136     * is shown.
137     *
138     * @param context Context used to display the shutdown progress dialog. This must be a context
139     *                suitable for displaying UI (aka Themable).
140     * @param reason code to pass to android_reboot() (e.g. "userrequested"), or null.
141     * @param confirm true if user confirmation is needed before shutting down.
142     */
143    public static void shutdown(final Context context, String reason, boolean confirm) {
144        mReboot = false;
145        mRebootSafeMode = false;
146        mReason = reason;
147        shutdownInner(context, confirm);
148    }
149
150    private static void shutdownInner(final Context context, boolean confirm) {
151        // ShutdownThread is called from many places, so best to verify here that the context passed
152        // in is themed.
153        context.assertRuntimeOverlayThemable();
154
155        // ensure that only one thread is trying to power down.
156        // any additional calls are just returned
157        synchronized (sIsStartedGuard) {
158            if (sIsStarted) {
159                Log.d(TAG, "Request to shutdown already running, returning.");
160                return;
161            }
162        }
163
164        final int longPressBehavior = context.getResources().getInteger(
165                        com.android.internal.R.integer.config_longPressOnPowerBehavior);
166        final int resourceId = mRebootSafeMode
167                ? com.android.internal.R.string.reboot_safemode_confirm
168                : (longPressBehavior == 2
169                        ? com.android.internal.R.string.shutdown_confirm_question
170                        : com.android.internal.R.string.shutdown_confirm);
171
172        Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
173
174        if (confirm) {
175            final CloseDialogReceiver closer = new CloseDialogReceiver(context);
176            if (sConfirmDialog != null) {
177                sConfirmDialog.dismiss();
178            }
179            sConfirmDialog = new AlertDialog.Builder(context)
180                    .setTitle(mRebootSafeMode
181                            ? com.android.internal.R.string.reboot_safemode_title
182                            : com.android.internal.R.string.power_off)
183                    .setMessage(resourceId)
184                    .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
185                        public void onClick(DialogInterface dialog, int which) {
186                            beginShutdownSequence(context);
187                        }
188                    })
189                    .setNegativeButton(com.android.internal.R.string.no, null)
190                    .create();
191            closer.dialog = sConfirmDialog;
192            sConfirmDialog.setOnDismissListener(closer);
193            sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
194            sConfirmDialog.show();
195        } else {
196            beginShutdownSequence(context);
197        }
198    }
199
200    private static class CloseDialogReceiver extends BroadcastReceiver
201            implements DialogInterface.OnDismissListener {
202        private Context mContext;
203        public Dialog dialog;
204
205        CloseDialogReceiver(Context context) {
206            mContext = context;
207            IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
208            context.registerReceiver(this, filter);
209        }
210
211        @Override
212        public void onReceive(Context context, Intent intent) {
213            dialog.cancel();
214        }
215
216        public void onDismiss(DialogInterface unused) {
217            mContext.unregisterReceiver(this);
218        }
219    }
220
221    /**
222     * Request a clean shutdown, waiting for subsystems to clean up their
223     * state etc.  Must be called from a Looper thread in which its UI
224     * is shown.
225     *
226     * @param context Context used to display the shutdown progress dialog. This must be a context
227     *                suitable for displaying UI (aka Themable).
228     * @param reason code to pass to the kernel (e.g. "recovery"), or null.
229     * @param confirm true if user confirmation is needed before shutting down.
230     */
231    public static void reboot(final Context context, String reason, boolean confirm) {
232        mReboot = true;
233        mRebootSafeMode = false;
234        mRebootHasProgressBar = false;
235        mReason = reason;
236        shutdownInner(context, confirm);
237    }
238
239    /**
240     * Request a reboot into safe mode.  Must be called from a Looper thread in which its UI
241     * is shown.
242     *
243     * @param context Context used to display the shutdown progress dialog. This must be a context
244     *                suitable for displaying UI (aka Themable).
245     * @param confirm true if user confirmation is needed before shutting down.
246     */
247    public static void rebootSafeMode(final Context context, boolean confirm) {
248        UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
249        if (um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
250            return;
251        }
252
253        mReboot = true;
254        mRebootSafeMode = true;
255        mRebootHasProgressBar = false;
256        mReason = null;
257        shutdownInner(context, confirm);
258    }
259
260    private static ProgressDialog showShutdownDialog(Context context) {
261        // Throw up a system dialog to indicate the device is rebooting / shutting down.
262        ProgressDialog pd = new ProgressDialog(context);
263
264        // Path 1: Reboot to recovery for update
265        //   Condition: mReason startswith REBOOT_RECOVERY_UPDATE
266        //
267        //  Path 1a: uncrypt needed
268        //   Condition: if /cache/recovery/uncrypt_file exists but
269        //              /cache/recovery/block.map doesn't.
270        //   UI: determinate progress bar (mRebootHasProgressBar == True)
271        //
272        // * Path 1a is expected to be removed once the GmsCore shipped on
273        //   device always calls uncrypt prior to reboot.
274        //
275        //  Path 1b: uncrypt already done
276        //   UI: spinning circle only (no progress bar)
277        //
278        // Path 2: Reboot to recovery for factory reset
279        //   Condition: mReason == REBOOT_RECOVERY
280        //   UI: spinning circle only (no progress bar)
281        //
282        // Path 3: Regular reboot / shutdown
283        //   Condition: Otherwise
284        //   UI: spinning circle only (no progress bar)
285
286        // mReason could be "recovery-update" or "recovery-update,quiescent".
287        if (mReason != null && mReason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
288            // We need the progress bar if uncrypt will be invoked during the
289            // reboot, which might be time-consuming.
290            mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists()
291                    && !(RecoverySystem.BLOCK_MAP_FILE.exists());
292            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
293            if (mRebootHasProgressBar) {
294                pd.setMax(100);
295                pd.setProgress(0);
296                pd.setIndeterminate(false);
297                pd.setProgressNumberFormat(null);
298                pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
299                pd.setMessage(context.getText(
300                            com.android.internal.R.string.reboot_to_update_prepare));
301            } else {
302                if (showSysuiReboot()) {
303                    return null;
304                }
305                pd.setIndeterminate(true);
306                pd.setMessage(context.getText(
307                            com.android.internal.R.string.reboot_to_update_reboot));
308            }
309        } else if (mReason != null && mReason.equals(PowerManager.REBOOT_RECOVERY)) {
310            if (RescueParty.isAttemptingFactoryReset()) {
311                // We're not actually doing a factory reset yet; we're rebooting
312                // to ask the user if they'd like to reset, so give them a less
313                // scary dialog message.
314                pd.setTitle(context.getText(com.android.internal.R.string.power_off));
315                pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
316                pd.setIndeterminate(true);
317            } else {
318                // Factory reset path. Set the dialog message accordingly.
319                pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
320                pd.setMessage(context.getText(
321                            com.android.internal.R.string.reboot_to_reset_message));
322                pd.setIndeterminate(true);
323            }
324        } else {
325            if (showSysuiReboot()) {
326                return null;
327            }
328            pd.setTitle(context.getText(com.android.internal.R.string.power_off));
329            pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
330            pd.setIndeterminate(true);
331        }
332        pd.setCancelable(false);
333        pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
334
335        pd.show();
336        return pd;
337    }
338
339    private static boolean showSysuiReboot() {
340        Log.d(TAG, "Attempting to use SysUI shutdown UI");
341        try {
342            StatusBarManagerInternal service = LocalServices.getService(
343                    StatusBarManagerInternal.class);
344            if (service.showShutdownUi(mReboot, mReason)) {
345                // Sysui will handle shutdown UI.
346                Log.d(TAG, "SysUI handling shutdown UI");
347                return true;
348            }
349        } catch (Exception e) {
350            // If anything went wrong, ignore it and use fallback ui
351        }
352        Log.d(TAG, "SysUI is unavailable");
353        return false;
354    }
355
356    private static void beginShutdownSequence(Context context) {
357        synchronized (sIsStartedGuard) {
358            if (sIsStarted) {
359                Log.d(TAG, "Shutdown sequence already running, returning.");
360                return;
361            }
362            sIsStarted = true;
363        }
364
365        sInstance.mProgressDialog = showShutdownDialog(context);
366        sInstance.mContext = context;
367        sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
368
369        // make sure we never fall asleep again
370        sInstance.mCpuWakeLock = null;
371        try {
372            sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
373                    PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
374            sInstance.mCpuWakeLock.setReferenceCounted(false);
375            sInstance.mCpuWakeLock.acquire();
376        } catch (SecurityException e) {
377            Log.w(TAG, "No permission to acquire wake lock", e);
378            sInstance.mCpuWakeLock = null;
379        }
380
381        // also make sure the screen stays on for better user experience
382        sInstance.mScreenWakeLock = null;
383        if (sInstance.mPowerManager.isScreenOn()) {
384            try {
385                sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
386                        PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
387                sInstance.mScreenWakeLock.setReferenceCounted(false);
388                sInstance.mScreenWakeLock.acquire();
389            } catch (SecurityException e) {
390                Log.w(TAG, "No permission to acquire wake lock", e);
391                sInstance.mScreenWakeLock = null;
392            }
393        }
394
395        if (SecurityLog.isLoggingEnabled()) {
396            SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);
397        }
398
399        // start the thread that initiates shutdown
400        sInstance.mHandler = new Handler() {
401        };
402        sInstance.start();
403    }
404
405    void actionDone() {
406        synchronized (mActionDoneSync) {
407            mActionDone = true;
408            mActionDoneSync.notifyAll();
409        }
410    }
411
412    /**
413     * Makes sure we handle the shutdown gracefully.
414     * Shuts off power regardless of radio state if the allotted time has passed.
415     */
416    public void run() {
417        TimingsTraceLog shutdownTimingLog = newTimingsLog();
418        shutdownTimingLog.traceBegin("SystemServerShutdown");
419        metricShutdownStart();
420        metricStarted(METRIC_SYSTEM_SERVER);
421
422        BroadcastReceiver br = new BroadcastReceiver() {
423            @Override public void onReceive(Context context, Intent intent) {
424                // We don't allow apps to cancel this, so ignore the result.
425                actionDone();
426            }
427        };
428
429        /*
430         * Write a system property in case the system_server reboots before we
431         * get to the actual hardware restart. If that happens, we'll retry at
432         * the beginning of the SystemServer startup.
433         */
434        {
435            String reason = (mReboot ? "1" : "0") + (mReason != null ? mReason : "");
436            SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
437        }
438
439        /*
440         * If we are rebooting into safe mode, write a system property
441         * indicating so.
442         */
443        if (mRebootSafeMode) {
444            SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
445        }
446
447        metricStarted(METRIC_SEND_BROADCAST);
448        shutdownTimingLog.traceBegin("SendShutdownBroadcast");
449        Log.i(TAG, "Sending shutdown broadcast...");
450
451        // First send the high-level shut down broadcast.
452        mActionDone = false;
453        Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
454        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
455        mContext.sendOrderedBroadcastAsUser(intent,
456                UserHandle.ALL, null, br, mHandler, 0, null, null);
457
458        final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
459        synchronized (mActionDoneSync) {
460            while (!mActionDone) {
461                long delay = endTime - SystemClock.elapsedRealtime();
462                if (delay <= 0) {
463                    Log.w(TAG, "Shutdown broadcast timed out");
464                    break;
465                } else if (mRebootHasProgressBar) {
466                    int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 *
467                            BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME);
468                    sInstance.setRebootProgress(status, null);
469                }
470                try {
471                    mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));
472                } catch (InterruptedException e) {
473                }
474            }
475        }
476        if (mRebootHasProgressBar) {
477            sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);
478        }
479        shutdownTimingLog.traceEnd(); // SendShutdownBroadcast
480        metricEnded(METRIC_SEND_BROADCAST);
481
482        Log.i(TAG, "Shutting down activity manager...");
483        shutdownTimingLog.traceBegin("ShutdownActivityManager");
484        metricStarted(METRIC_AM);
485
486        final IActivityManager am =
487                IActivityManager.Stub.asInterface(ServiceManager.checkService("activity"));
488        if (am != null) {
489            try {
490                am.shutdown(MAX_BROADCAST_TIME);
491            } catch (RemoteException e) {
492            }
493        }
494        if (mRebootHasProgressBar) {
495            sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
496        }
497        shutdownTimingLog.traceEnd();// ShutdownActivityManager
498        metricEnded(METRIC_AM);
499
500        Log.i(TAG, "Shutting down package manager...");
501        shutdownTimingLog.traceBegin("ShutdownPackageManager");
502        metricStarted(METRIC_PM);
503
504        final PackageManagerService pm = (PackageManagerService)
505            ServiceManager.getService("package");
506        if (pm != null) {
507            pm.shutdown();
508        }
509        if (mRebootHasProgressBar) {
510            sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
511        }
512        shutdownTimingLog.traceEnd(); // ShutdownPackageManager
513        metricEnded(METRIC_PM);
514
515        // Shutdown radios.
516        shutdownTimingLog.traceBegin("ShutdownRadios");
517        metricStarted(METRIC_RADIOS);
518        shutdownRadios(MAX_RADIO_WAIT_TIME);
519        if (mRebootHasProgressBar) {
520            sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
521        }
522        shutdownTimingLog.traceEnd(); // ShutdownRadios
523        metricEnded(METRIC_RADIOS);
524
525        if (mRebootHasProgressBar) {
526            sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);
527
528            // If it's to reboot to install an update and uncrypt hasn't been
529            // done yet, trigger it now.
530            uncrypt();
531        }
532
533        shutdownTimingLog.traceEnd(); // SystemServerShutdown
534        metricEnded(METRIC_SYSTEM_SERVER);
535        saveMetrics(mReboot, mReason);
536        // Remaining work will be done by init, including vold shutdown
537        rebootOrShutdown(mContext, mReboot, mReason);
538    }
539
540    private static TimingsTraceLog newTimingsLog() {
541        return new TimingsTraceLog("ShutdownTiming", Trace.TRACE_TAG_SYSTEM_SERVER);
542    }
543
544    private static void metricStarted(String metricKey) {
545        synchronized (TRON_METRICS) {
546            TRON_METRICS.put(metricKey, -1 * SystemClock.elapsedRealtime());
547        }
548    }
549
550    private static void metricEnded(String metricKey) {
551        synchronized (TRON_METRICS) {
552            TRON_METRICS
553                    .put(metricKey, SystemClock.elapsedRealtime() + TRON_METRICS.get(metricKey));
554        }
555    }
556
557    private static void metricShutdownStart() {
558        synchronized (TRON_METRICS) {
559            TRON_METRICS.put(METRIC_SHUTDOWN_TIME_START, System.currentTimeMillis());
560        }
561    }
562
563    private void setRebootProgress(final int progress, final CharSequence message) {
564        mHandler.post(new Runnable() {
565            @Override
566            public void run() {
567                if (mProgressDialog != null) {
568                    mProgressDialog.setProgress(progress);
569                    if (message != null) {
570                        mProgressDialog.setMessage(message);
571                    }
572                }
573            }
574        });
575    }
576
577    private void shutdownRadios(final int timeout) {
578        // If a radio is wedged, disabling it may hang so we do this work in another thread,
579        // just in case.
580        final long endTime = SystemClock.elapsedRealtime() + timeout;
581        final boolean[] done = new boolean[1];
582        Thread t = new Thread() {
583            public void run() {
584                TimingsTraceLog shutdownTimingsTraceLog = newTimingsLog();
585                boolean radioOff;
586
587                final ITelephony phone =
588                        ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
589
590                try {
591                    radioOff = phone == null || !phone.needMobileRadioShutdown();
592                    if (!radioOff) {
593                        Log.w(TAG, "Turning off cellular radios...");
594                        metricStarted(METRIC_RADIO);
595                        phone.shutdownMobileRadios();
596                    }
597                } catch (RemoteException ex) {
598                    Log.e(TAG, "RemoteException during radio shutdown", ex);
599                    radioOff = true;
600                }
601
602                Log.i(TAG, "Waiting for Radio...");
603
604                long delay = endTime - SystemClock.elapsedRealtime();
605                while (delay > 0) {
606                    if (mRebootHasProgressBar) {
607                        int status = (int)((timeout - delay) * 1.0 *
608                                (RADIO_STOP_PERCENT - PACKAGE_MANAGER_STOP_PERCENT) / timeout);
609                        status += PACKAGE_MANAGER_STOP_PERCENT;
610                        sInstance.setRebootProgress(status, null);
611                    }
612
613                    if (!radioOff) {
614                        try {
615                            radioOff = !phone.needMobileRadioShutdown();
616                        } catch (RemoteException ex) {
617                            Log.e(TAG, "RemoteException during radio shutdown", ex);
618                            radioOff = true;
619                        }
620                        if (radioOff) {
621                            Log.i(TAG, "Radio turned off.");
622                            metricEnded(METRIC_RADIO);
623                            shutdownTimingsTraceLog
624                                    .logDuration("ShutdownRadio", TRON_METRICS.get(METRIC_RADIO));
625                        }
626                    }
627
628                    if (radioOff) {
629                        Log.i(TAG, "Radio shutdown complete.");
630                        done[0] = true;
631                        break;
632                    }
633                    SystemClock.sleep(RADIOS_STATE_POLL_SLEEP_MS);
634                    delay = endTime - SystemClock.elapsedRealtime();
635                }
636            }
637        };
638
639        t.start();
640        try {
641            t.join(timeout);
642        } catch (InterruptedException ex) {
643        }
644        if (!done[0]) {
645            Log.w(TAG, "Timed out waiting for Radio shutdown.");
646        }
647    }
648
649    /**
650     * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
651     * or {@link #shutdown(Context, String, boolean)} instead.
652     *
653     * @param context Context used to vibrate or null without vibration
654     * @param reboot true to reboot or false to shutdown
655     * @param reason reason for reboot/shutdown
656     */
657    public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
658        if (reboot) {
659            Log.i(TAG, "Rebooting, reason: " + reason);
660            PowerManagerService.lowLevelReboot(reason);
661            Log.e(TAG, "Reboot failed, will attempt shutdown instead");
662            reason = null;
663        } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
664            // vibrate before shutting down
665            Vibrator vibrator = new SystemVibrator(context);
666            try {
667                vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
668            } catch (Exception e) {
669                // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
670                Log.w(TAG, "Failed to vibrate during shutdown.", e);
671            }
672
673            // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
674            try {
675                Thread.sleep(SHUTDOWN_VIBRATE_MS);
676            } catch (InterruptedException unused) {
677            }
678        }
679        // Shutdown power
680        Log.i(TAG, "Performing low-level shutdown...");
681        PowerManagerService.lowLevelShutdown(reason);
682    }
683
684    private static void saveMetrics(boolean reboot, String reason) {
685        StringBuilder metricValue = new StringBuilder();
686        metricValue.append("reboot:");
687        metricValue.append(reboot ? "y" : "n");
688        metricValue.append(",").append("reason:").append(reason);
689        final int metricsSize = TRON_METRICS.size();
690        for (int i = 0; i < metricsSize; i++) {
691            final String name = TRON_METRICS.keyAt(i);
692            final long value = TRON_METRICS.valueAt(i);
693            if (value < 0) {
694                Log.e(TAG, "metricEnded wasn't called for " + name);
695                continue;
696            }
697            metricValue.append(',').append(name).append(':').append(value);
698        }
699        File tmp = new File(METRICS_FILE_BASENAME + ".tmp");
700        boolean saved = false;
701        try (FileOutputStream fos = new FileOutputStream(tmp)) {
702            fos.write(metricValue.toString().getBytes(StandardCharsets.UTF_8));
703            saved = true;
704        } catch (IOException e) {
705            Log.e(TAG,"Cannot save shutdown metrics", e);
706        }
707        if (saved) {
708            tmp.renameTo(new File(METRICS_FILE_BASENAME + ".txt"));
709        }
710    }
711
712    private void uncrypt() {
713        Log.i(TAG, "Calling uncrypt and monitoring the progress...");
714
715        final RecoverySystem.ProgressListener progressListener =
716                new RecoverySystem.ProgressListener() {
717            @Override
718            public void onProgress(int status) {
719                if (status >= 0 && status < 100) {
720                    // Scale down to [MOUNT_SERVICE_STOP_PERCENT, 100).
721                    status = (int)(status * (100.0 - MOUNT_SERVICE_STOP_PERCENT) / 100);
722                    status += MOUNT_SERVICE_STOP_PERCENT;
723                    CharSequence msg = mContext.getText(
724                            com.android.internal.R.string.reboot_to_update_package);
725                    sInstance.setRebootProgress(status, msg);
726                } else if (status == 100) {
727                    CharSequence msg = mContext.getText(
728                            com.android.internal.R.string.reboot_to_update_reboot);
729                    sInstance.setRebootProgress(status, msg);
730                } else {
731                    // Ignored
732                }
733            }
734        };
735
736        final boolean[] done = new boolean[1];
737        done[0] = false;
738        Thread t = new Thread() {
739            @Override
740            public void run() {
741                RecoverySystem rs = (RecoverySystem) mContext.getSystemService(
742                        Context.RECOVERY_SERVICE);
743                String filename = null;
744                try {
745                    filename = FileUtils.readTextFile(RecoverySystem.UNCRYPT_PACKAGE_FILE, 0, null);
746                    rs.processPackage(mContext, new File(filename), progressListener);
747                } catch (IOException e) {
748                    Log.e(TAG, "Error uncrypting file", e);
749                }
750                done[0] = true;
751            }
752        };
753        t.start();
754
755        try {
756            t.join(MAX_UNCRYPT_WAIT_TIME);
757        } catch (InterruptedException unused) {
758        }
759        if (!done[0]) {
760            Log.w(TAG, "Timed out waiting for uncrypt.");
761            final int uncryptTimeoutError = 100;
762            String timeoutMessage = String.format("uncrypt_time: %d\n" + "uncrypt_error: %d\n",
763                    MAX_UNCRYPT_WAIT_TIME / 1000, uncryptTimeoutError);
764            try {
765                FileUtils.stringToFile(RecoverySystem.UNCRYPT_STATUS_FILE, timeoutMessage);
766            } catch (IOException e) {
767                Log.e(TAG, "Failed to write timeout message to uncrypt status", e);
768            }
769        }
770    }
771}
772