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