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