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.content.pm.PackageManagerInternal; 31 import android.media.AudioAttributes; 32 import android.os.FileUtils; 33 import android.os.Handler; 34 import android.os.PowerManager; 35 import android.os.RecoverySystem; 36 import android.os.RemoteException; 37 import android.os.ServiceManager; 38 import android.os.SystemClock; 39 import android.os.SystemProperties; 40 import android.os.SystemVibrator; 41 import android.os.Trace; 42 import android.os.UserHandle; 43 import android.os.UserManager; 44 import android.os.Vibrator; 45 import android.telephony.TelephonyManager; 46 import android.util.ArrayMap; 47 import android.util.Log; 48 import android.util.Slog; 49 import android.util.TimingsTraceLog; 50 import android.view.WindowManager; 51 52 import com.android.server.LocalServices; 53 import com.android.server.RescueParty; 54 import com.android.server.pm.PackageManagerService; 55 import com.android.server.statusbar.StatusBarManagerInternal; 56 57 import java.io.File; 58 import java.io.FileOutputStream; 59 import java.io.IOException; 60 import java.nio.charset.StandardCharsets; 61 62 public final class ShutdownThread extends Thread { 63 // constants 64 private static final String TAG = "ShutdownThread"; 65 private static final int ACTION_DONE_POLL_WAIT_MS = 500; 66 private static final int RADIOS_STATE_POLL_SLEEP_MS = 100; 67 // maximum time we wait for the shutdown broadcast before going on. 68 private static final int MAX_BROADCAST_TIME = 10 * 1000; 69 private static final int MAX_CHECK_POINTS_DUMP_WAIT_TIME = 10 * 1000; 70 private static final int MAX_RADIO_WAIT_TIME = 12 * 1000; 71 private static final int MAX_UNCRYPT_WAIT_TIME = 15 * 60 * 1000; 72 // constants for progress bar. the values are roughly estimated based on timeout. 73 private static final int BROADCAST_STOP_PERCENT = 2; 74 private static final int ACTIVITY_MANAGER_STOP_PERCENT = 4; 75 private static final int PACKAGE_MANAGER_STOP_PERCENT = 6; 76 private static final int RADIO_STOP_PERCENT = 18; 77 private static final int MOUNT_SERVICE_STOP_PERCENT = 20; 78 79 // length of vibration before shutting down 80 private static final int SHUTDOWN_VIBRATE_MS = 500; 81 82 // state tracking 83 private static final Object sIsStartedGuard = new Object(); 84 private static boolean sIsStarted = false; 85 86 private static boolean mReboot; 87 private static boolean mRebootSafeMode; 88 private static boolean mRebootHasProgressBar; 89 private static String mReason; 90 91 // Provides shutdown assurance in case the system_server is killed 92 public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested"; 93 94 // Indicates whether we are rebooting into safe mode 95 public static final String REBOOT_SAFEMODE_PROPERTY = "persist.sys.safemode"; 96 public static final String RO_SAFEMODE_PROPERTY = "ro.sys.safemode"; 97 98 // static instance of this thread 99 private static final ShutdownThread sInstance = new ShutdownThread(); 100 101 private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() 102 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 103 .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) 104 .build(); 105 106 // Metrics that will be reported to tron after reboot 107 private static final ArrayMap<String, Long> TRON_METRICS = new ArrayMap<>(); 108 109 // File to use for saving shutdown metrics 110 private static final String METRICS_FILE_BASENAME = "/data/system/shutdown-metrics"; 111 // File to use for saving shutdown check points 112 private static final String CHECK_POINTS_FILE_BASENAME = 113 "/data/system/shutdown-checkpoints/checkpoints"; 114 115 // Metrics names to be persisted in shutdown-metrics file 116 private static String METRIC_SYSTEM_SERVER = "shutdown_system_server"; 117 private static String METRIC_SEND_BROADCAST = "shutdown_send_shutdown_broadcast"; 118 private static String METRIC_AM = "shutdown_activity_manager"; 119 private static String METRIC_PM = "shutdown_package_manager"; 120 private static String METRIC_RADIOS = "shutdown_radios"; 121 private static String METRIC_RADIO = "shutdown_radio"; 122 private static String METRIC_SHUTDOWN_TIME_START = "begin_shutdown"; 123 124 private final Object mActionDoneSync = new Object(); 125 private boolean mActionDone; 126 private Context mContext; 127 private PowerManager mPowerManager; 128 private PowerManager.WakeLock mCpuWakeLock; 129 private PowerManager.WakeLock mScreenWakeLock; 130 private Handler mHandler; 131 132 private static AlertDialog sConfirmDialog; 133 private ProgressDialog mProgressDialog; 134 ShutdownThread()135 private ShutdownThread() { 136 } 137 138 /** 139 * Request a clean shutdown, waiting for subsystems to clean up their 140 * state etc. Must be called from a Looper thread in which its UI 141 * is shown. 142 * 143 * @param context Context used to display the shutdown progress dialog. This must be a context 144 * suitable for displaying UI (aka Themable). 145 * @param reason code to pass to android_reboot() (e.g. "userrequested"), or null. 146 * @param confirm true if user confirmation is needed before shutting down. 147 */ shutdown(final Context context, String reason, boolean confirm)148 public static void shutdown(final Context context, String reason, boolean confirm) { 149 mReboot = false; 150 mRebootSafeMode = false; 151 mReason = reason; 152 shutdownInner(context, confirm); 153 } 154 shutdownInner(final Context context, boolean confirm)155 private static void shutdownInner(final Context context, boolean confirm) { 156 // ShutdownThread is called from many places, so best to verify here that the context passed 157 // in is themed. 158 context.assertRuntimeOverlayThemable(); 159 160 // ensure that only one thread is trying to power down. 161 // any additional calls are just returned 162 synchronized (sIsStartedGuard) { 163 if (sIsStarted) { 164 Log.d(TAG, "Request to shutdown already running, returning."); 165 return; 166 } 167 } 168 169 // Add checkpoint for this shutdown attempt. The user might still cancel the dialog, but 170 // this point preserves the system trace of the trigger point of the ShutdownThread. 171 ShutdownCheckPoints.recordCheckPoint(/* reason= */ null); 172 173 final int longPressBehavior = context.getResources().getInteger( 174 com.android.internal.R.integer.config_longPressOnPowerBehavior); 175 final int resourceId = mRebootSafeMode 176 ? com.android.internal.R.string.reboot_safemode_confirm 177 : (longPressBehavior == 2 178 ? com.android.internal.R.string.shutdown_confirm_question 179 : com.android.internal.R.string.shutdown_confirm); 180 181 Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior); 182 183 if (confirm) { 184 final CloseDialogReceiver closer = new CloseDialogReceiver(context); 185 if (sConfirmDialog != null) { 186 sConfirmDialog.dismiss(); 187 } 188 sConfirmDialog = new AlertDialog.Builder(context) 189 .setTitle(mRebootSafeMode 190 ? com.android.internal.R.string.reboot_safemode_title 191 : com.android.internal.R.string.power_off) 192 .setMessage(resourceId) 193 .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() { 194 public void onClick(DialogInterface dialog, int which) { 195 beginShutdownSequence(context); 196 } 197 }) 198 .setNegativeButton(com.android.internal.R.string.no, null) 199 .create(); 200 closer.dialog = sConfirmDialog; 201 sConfirmDialog.setOnDismissListener(closer); 202 sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 203 sConfirmDialog.show(); 204 } else { 205 beginShutdownSequence(context); 206 } 207 } 208 209 private static class CloseDialogReceiver extends BroadcastReceiver 210 implements DialogInterface.OnDismissListener { 211 private Context mContext; 212 public Dialog dialog; 213 CloseDialogReceiver(Context context)214 CloseDialogReceiver(Context context) { 215 mContext = context; 216 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 217 context.registerReceiver(this, filter, Context.RECEIVER_EXPORTED); 218 } 219 220 @Override onReceive(Context context, Intent intent)221 public void onReceive(Context context, Intent intent) { 222 dialog.cancel(); 223 } 224 onDismiss(DialogInterface unused)225 public void onDismiss(DialogInterface unused) { 226 mContext.unregisterReceiver(this); 227 } 228 } 229 230 /** 231 * Request a clean shutdown, waiting for subsystems to clean up their 232 * state etc. Must be called from a Looper thread in which its UI 233 * is shown. 234 * 235 * @param context Context used to display the shutdown progress dialog. This must be a context 236 * suitable for displaying UI (aka Themable). 237 * @param reason code to pass to the kernel (e.g. "recovery"), or null. 238 * @param confirm true if user confirmation is needed before shutting down. 239 */ reboot(final Context context, String reason, boolean confirm)240 public static void reboot(final Context context, String reason, boolean confirm) { 241 mReboot = true; 242 mRebootSafeMode = false; 243 mRebootHasProgressBar = false; 244 mReason = reason; 245 shutdownInner(context, confirm); 246 } 247 248 /** 249 * Request a reboot into safe mode. Must be called from a Looper thread in which its UI 250 * is shown. 251 * 252 * @param context Context used to display the shutdown progress dialog. This must be a context 253 * suitable for displaying UI (aka Themable). 254 * @param confirm true if user confirmation is needed before shutting down. 255 */ rebootSafeMode(final Context context, boolean confirm)256 public static void rebootSafeMode(final Context context, boolean confirm) { 257 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 258 if (um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { 259 return; 260 } 261 262 mReboot = true; 263 mRebootSafeMode = true; 264 mRebootHasProgressBar = false; 265 mReason = null; 266 shutdownInner(context, confirm); 267 } 268 showShutdownDialog(Context context)269 private static ProgressDialog showShutdownDialog(Context context) { 270 // Throw up a system dialog to indicate the device is rebooting / shutting down. 271 ProgressDialog pd = new ProgressDialog(context); 272 273 // Path 1: Reboot to recovery for update 274 // Condition: mReason startswith REBOOT_RECOVERY_UPDATE 275 // 276 // Path 1a: uncrypt needed 277 // Condition: if /cache/recovery/uncrypt_file exists but 278 // /cache/recovery/block.map doesn't. 279 // UI: determinate progress bar (mRebootHasProgressBar == True) 280 // 281 // * Path 1a is expected to be removed once the GmsCore shipped on 282 // device always calls uncrypt prior to reboot. 283 // 284 // Path 1b: uncrypt already done 285 // UI: spinning circle only (no progress bar) 286 // 287 // Path 2: Reboot to recovery for factory reset 288 // Condition: mReason == REBOOT_RECOVERY 289 // UI: spinning circle only (no progress bar) 290 // 291 // Path 3: Regular reboot / shutdown 292 // Condition: Otherwise 293 // UI: spinning circle only (no progress bar) 294 295 // mReason could be "recovery-update" or "recovery-update,quiescent". 296 if (mReason != null && mReason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) { 297 // We need the progress bar if uncrypt will be invoked during the 298 // reboot, which might be time-consuming. 299 mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists() 300 && !(RecoverySystem.BLOCK_MAP_FILE.exists()); 301 pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title)); 302 if (mRebootHasProgressBar) { 303 pd.setMax(100); 304 pd.setProgress(0); 305 pd.setIndeterminate(false); 306 pd.setProgressNumberFormat(null); 307 pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); 308 pd.setMessage(context.getText( 309 com.android.internal.R.string.reboot_to_update_prepare)); 310 } else { 311 if (showSysuiReboot()) { 312 return null; 313 } 314 pd.setIndeterminate(true); 315 pd.setMessage(context.getText( 316 com.android.internal.R.string.reboot_to_update_reboot)); 317 } 318 } else if (mReason != null && mReason.equals(PowerManager.REBOOT_RECOVERY)) { 319 if (RescueParty.isAttemptingFactoryReset()) { 320 // We're not actually doing a factory reset yet; we're rebooting 321 // to ask the user if they'd like to reset, so give them a less 322 // scary dialog message. 323 pd.setTitle(context.getText(com.android.internal.R.string.power_off)); 324 pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress)); 325 pd.setIndeterminate(true); 326 } else if (showSysuiReboot()) { 327 return null; 328 } else { 329 // Factory reset path. Set the dialog message accordingly. 330 pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title)); 331 pd.setMessage(context.getText( 332 com.android.internal.R.string.reboot_to_reset_message)); 333 pd.setIndeterminate(true); 334 } 335 } else { 336 if (showSysuiReboot()) { 337 return null; 338 } 339 pd.setTitle(context.getText(com.android.internal.R.string.power_off)); 340 pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress)); 341 pd.setIndeterminate(true); 342 } 343 pd.setCancelable(false); 344 pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 345 346 pd.show(); 347 return pd; 348 } 349 showSysuiReboot()350 private static boolean showSysuiReboot() { 351 Log.d(TAG, "Attempting to use SysUI shutdown UI"); 352 try { 353 StatusBarManagerInternal service = LocalServices.getService( 354 StatusBarManagerInternal.class); 355 if (service.showShutdownUi(mReboot, mReason)) { 356 // Sysui will handle shutdown UI. 357 Log.d(TAG, "SysUI handling shutdown UI"); 358 return true; 359 } 360 } catch (Exception e) { 361 // If anything went wrong, ignore it and use fallback ui 362 } 363 Log.d(TAG, "SysUI is unavailable"); 364 return false; 365 } 366 beginShutdownSequence(Context context)367 private static void beginShutdownSequence(Context context) { 368 synchronized (sIsStartedGuard) { 369 if (sIsStarted) { 370 Log.d(TAG, "Shutdown sequence already running, returning."); 371 return; 372 } 373 sIsStarted = true; 374 } 375 376 sInstance.mProgressDialog = showShutdownDialog(context); 377 sInstance.mContext = context; 378 sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 379 380 // make sure we never fall asleep again 381 sInstance.mCpuWakeLock = null; 382 try { 383 sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock( 384 PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu"); 385 sInstance.mCpuWakeLock.setReferenceCounted(false); 386 sInstance.mCpuWakeLock.acquire(); 387 } catch (SecurityException e) { 388 Log.w(TAG, "No permission to acquire wake lock", e); 389 sInstance.mCpuWakeLock = null; 390 } 391 392 // also make sure the screen stays on for better user experience 393 sInstance.mScreenWakeLock = null; 394 if (sInstance.mPowerManager.isScreenOn()) { 395 try { 396 sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock( 397 PowerManager.FULL_WAKE_LOCK, TAG + "-screen"); 398 sInstance.mScreenWakeLock.setReferenceCounted(false); 399 sInstance.mScreenWakeLock.acquire(); 400 } catch (SecurityException e) { 401 Log.w(TAG, "No permission to acquire wake lock", e); 402 sInstance.mScreenWakeLock = null; 403 } 404 } 405 406 if (SecurityLog.isLoggingEnabled()) { 407 SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN); 408 } 409 410 // start the thread that initiates shutdown 411 sInstance.mHandler = new Handler() { 412 }; 413 sInstance.start(); 414 } 415 actionDone()416 void actionDone() { 417 synchronized (mActionDoneSync) { 418 mActionDone = true; 419 mActionDoneSync.notifyAll(); 420 } 421 } 422 423 /** 424 * Makes sure we handle the shutdown gracefully. 425 * Shuts off power regardless of radio state if the allotted time has passed. 426 */ run()427 public void run() { 428 TimingsTraceLog shutdownTimingLog = newTimingsLog(); 429 shutdownTimingLog.traceBegin("SystemServerShutdown"); 430 metricShutdownStart(); 431 metricStarted(METRIC_SYSTEM_SERVER); 432 433 // Start dumping check points for this shutdown in a separate thread. 434 Thread dumpCheckPointsThread = ShutdownCheckPoints.newDumpThread( 435 new File(CHECK_POINTS_FILE_BASENAME)); 436 dumpCheckPointsThread.start(); 437 438 BroadcastReceiver br = new BroadcastReceiver() { 439 @Override public void onReceive(Context context, Intent intent) { 440 // We don't allow apps to cancel this, so ignore the result. 441 actionDone(); 442 } 443 }; 444 445 /* 446 * Write a system property in case the system_server reboots before we 447 * get to the actual hardware restart. If that happens, we'll retry at 448 * the beginning of the SystemServer startup. 449 */ 450 { 451 String reason = (mReboot ? "1" : "0") + (mReason != null ? mReason : ""); 452 SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason); 453 } 454 455 /* 456 * If we are rebooting into safe mode, write a system property 457 * indicating so. 458 */ 459 if (mRebootSafeMode) { 460 SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1"); 461 } 462 463 shutdownTimingLog.traceBegin("DumpPreRebootInfo"); 464 try { 465 Slog.i(TAG, "Logging pre-reboot information..."); 466 PreRebootLogger.log(mContext); 467 } catch (Exception e) { 468 Slog.e(TAG, "Failed to log pre-reboot information", e); 469 } 470 shutdownTimingLog.traceEnd(); // DumpPreRebootInfo 471 472 metricStarted(METRIC_SEND_BROADCAST); 473 shutdownTimingLog.traceBegin("SendShutdownBroadcast"); 474 Log.i(TAG, "Sending shutdown broadcast..."); 475 476 // First send the high-level shut down broadcast. 477 mActionDone = false; 478 Intent intent = new Intent(Intent.ACTION_SHUTDOWN); 479 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY); 480 mContext.sendOrderedBroadcastAsUser(intent, 481 UserHandle.ALL, null, br, mHandler, 0, null, null); 482 483 final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME; 484 synchronized (mActionDoneSync) { 485 while (!mActionDone) { 486 long delay = endTime - SystemClock.elapsedRealtime(); 487 if (delay <= 0) { 488 Log.w(TAG, "Shutdown broadcast timed out"); 489 break; 490 } else if (mRebootHasProgressBar) { 491 int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 * 492 BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME); 493 sInstance.setRebootProgress(status, null); 494 } 495 try { 496 mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS)); 497 } catch (InterruptedException e) { 498 } 499 } 500 } 501 if (mRebootHasProgressBar) { 502 sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null); 503 } 504 shutdownTimingLog.traceEnd(); // SendShutdownBroadcast 505 metricEnded(METRIC_SEND_BROADCAST); 506 507 Log.i(TAG, "Shutting down activity manager..."); 508 shutdownTimingLog.traceBegin("ShutdownActivityManager"); 509 metricStarted(METRIC_AM); 510 511 final IActivityManager am = 512 IActivityManager.Stub.asInterface(ServiceManager.checkService("activity")); 513 if (am != null) { 514 try { 515 am.shutdown(MAX_BROADCAST_TIME); 516 } catch (RemoteException e) { 517 } 518 } 519 if (mRebootHasProgressBar) { 520 sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null); 521 } 522 shutdownTimingLog.traceEnd();// ShutdownActivityManager 523 metricEnded(METRIC_AM); 524 525 Log.i(TAG, "Shutting down package manager..."); 526 shutdownTimingLog.traceBegin("ShutdownPackageManager"); 527 metricStarted(METRIC_PM); 528 529 final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); 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