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