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 ShutdownThread()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 */ shutdown(final Context context, String reason, boolean confirm)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 shutdownInner(final Context context, boolean confirm)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 CloseDialogReceiver(Context context)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 onReceive(Context context, Intent intent)212 public void onReceive(Context context, Intent intent) { 213 dialog.cancel(); 214 } 215 onDismiss(DialogInterface unused)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 */ reboot(final Context context, String reason, boolean confirm)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 */ rebootSafeMode(final Context context, boolean confirm)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 showShutdownDialog(Context context)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 showSysuiReboot()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 beginShutdownSequence(Context context)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 actionDone()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 */ run()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 newTimingsLog()540 private static TimingsTraceLog newTimingsLog() { 541 return new TimingsTraceLog("ShutdownTiming", Trace.TRACE_TAG_SYSTEM_SERVER); 542 } 543 metricStarted(String metricKey)544 private static void metricStarted(String metricKey) { 545 synchronized (TRON_METRICS) { 546 TRON_METRICS.put(metricKey, -1 * SystemClock.elapsedRealtime()); 547 } 548 } 549 metricEnded(String metricKey)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 metricShutdownStart()557 private static void metricShutdownStart() { 558 synchronized (TRON_METRICS) { 559 TRON_METRICS.put(METRIC_SHUTDOWN_TIME_START, System.currentTimeMillis()); 560 } 561 } 562 setRebootProgress(final int progress, final CharSequence message)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 shutdownRadios(final int timeout)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 */ rebootOrShutdown(final Context context, boolean reboot, String reason)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 saveMetrics(boolean reboot, String reason)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 uncrypt()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