Home | History | Annotate | Download | only in power
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 
     18 package com.android.server.power;
     19 
     20 import android.app.AlertDialog;
     21 import android.app.Dialog;
     22 import android.app.IActivityManager;
     23 import android.app.ProgressDialog;
     24 import android.app.admin.SecurityLog;
     25 import android.content.BroadcastReceiver;
     26 import android.content.Context;
     27 import android.content.DialogInterface;
     28 import android.content.Intent;
     29 import android.content.IntentFilter;
     30 import android.media.AudioAttributes;
     31 import android.os.FileUtils;
     32 import android.os.Handler;
     33 import android.os.PowerManager;
     34 import android.os.RecoverySystem;
     35 import android.os.RemoteException;
     36 import android.os.ServiceManager;
     37 import android.os.SystemClock;
     38 import android.os.SystemProperties;
     39 import android.os.SystemVibrator;
     40 import android.os.Trace;
     41 import android.os.UserHandle;
     42 import android.os.UserManager;
     43 import android.os.Vibrator;
     44 import android.util.ArrayMap;
     45 import android.util.Log;
     46 import android.util.TimingsTraceLog;
     47 import android.view.WindowManager;
     48 
     49 import com.android.internal.telephony.ITelephony;
     50 import com.android.server.RescueParty;
     51 import com.android.server.LocalServices;
     52 import com.android.server.pm.PackageManagerService;
     53 import com.android.server.statusbar.StatusBarManagerInternal;
     54 
     55 import java.io.File;
     56 import java.io.FileOutputStream;
     57 import java.io.IOException;
     58 import java.nio.charset.StandardCharsets;
     59 
     60 public 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