1 /* 2 * Copyright (C) 2010 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 package android.os; 18 19 import static java.nio.charset.StandardCharsets.UTF_8; 20 21 import android.annotation.RequiresPermission; 22 import android.annotation.SuppressLint; 23 import android.annotation.SystemApi; 24 import android.annotation.SystemService; 25 import android.app.PendingIntent; 26 import android.content.BroadcastReceiver; 27 import android.content.ContentResolver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.pm.PackageManager; 32 import android.provider.Settings; 33 import android.telephony.euicc.EuiccManager; 34 import android.text.TextUtils; 35 import android.util.Log; 36 import android.view.Display; 37 import android.view.WindowManager; 38 39 import com.android.internal.logging.MetricsLogger; 40 41 import libcore.io.Streams; 42 43 import java.io.BufferedReader; 44 import java.io.ByteArrayInputStream; 45 import java.io.File; 46 import java.io.FileInputStream; 47 import java.io.FileNotFoundException; 48 import java.io.FileReader; 49 import java.io.FileWriter; 50 import java.io.IOException; 51 import java.io.InputStream; 52 import java.io.RandomAccessFile; 53 import java.security.GeneralSecurityException; 54 import java.security.PublicKey; 55 import java.security.SignatureException; 56 import java.security.cert.CertificateFactory; 57 import java.security.cert.X509Certificate; 58 import java.util.ArrayList; 59 import java.util.Enumeration; 60 import java.util.HashSet; 61 import java.util.Locale; 62 import java.util.concurrent.CountDownLatch; 63 import java.util.concurrent.TimeUnit; 64 import java.util.zip.ZipEntry; 65 import java.util.zip.ZipFile; 66 import java.util.zip.ZipInputStream; 67 68 import sun.security.pkcs.PKCS7; 69 import sun.security.pkcs.SignerInfo; 70 71 /** 72 * RecoverySystem contains methods for interacting with the Android 73 * recovery system (the separate partition that can be used to install 74 * system updates, wipe user data, etc.) 75 */ 76 @SystemService(Context.RECOVERY_SERVICE) 77 public class RecoverySystem { 78 private static final String TAG = "RecoverySystem"; 79 80 /** 81 * Default location of zip file containing public keys (X509 82 * certs) authorized to sign OTA updates. 83 */ 84 private static final File DEFAULT_KEYSTORE = 85 new File("/system/etc/security/otacerts.zip"); 86 87 /** Send progress to listeners no more often than this (in ms). */ 88 private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500; 89 90 private static final long DEFAULT_EUICC_FACTORY_RESET_TIMEOUT_MILLIS = 30000L; // 30 s 91 92 private static final long MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS = 5000L; // 5 s 93 94 private static final long MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS = 60000L; // 60 s 95 96 /** Used to communicate with recovery. See bootable/recovery/recovery.cpp. */ 97 private static final File RECOVERY_DIR = new File("/cache/recovery"); 98 private static final File LOG_FILE = new File(RECOVERY_DIR, "log"); 99 private static final File LAST_INSTALL_FILE = new File(RECOVERY_DIR, "last_install"); 100 private static final String LAST_PREFIX = "last_"; 101 private static final String ACTION_EUICC_FACTORY_RESET = 102 "com.android.internal.action.EUICC_FACTORY_RESET"; 103 104 /** 105 * The recovery image uses this file to identify the location (i.e. blocks) 106 * of an OTA package on the /data partition. The block map file is 107 * generated by uncrypt. 108 * 109 * @hide 110 */ 111 public static final File BLOCK_MAP_FILE = new File(RECOVERY_DIR, "block.map"); 112 113 /** 114 * UNCRYPT_PACKAGE_FILE stores the filename to be uncrypt'd, which will be 115 * read by uncrypt. 116 * 117 * @hide 118 */ 119 public static final File UNCRYPT_PACKAGE_FILE = new File(RECOVERY_DIR, "uncrypt_file"); 120 121 /** 122 * UNCRYPT_STATUS_FILE stores the time cost (and error code in the case of a failure) 123 * of uncrypt. 124 * 125 * @hide 126 */ 127 public static final File UNCRYPT_STATUS_FILE = new File(RECOVERY_DIR, "uncrypt_status"); 128 129 // Length limits for reading files. 130 private static final int LOG_FILE_MAX_LENGTH = 64 * 1024; 131 132 // Prevent concurrent execution of requests. 133 private static final Object sRequestLock = new Object(); 134 135 private final IRecoverySystem mService; 136 137 /** 138 * Interface definition for a callback to be invoked regularly as 139 * verification proceeds. 140 */ 141 public interface ProgressListener { 142 /** 143 * Called periodically as the verification progresses. 144 * 145 * @param progress the approximate percentage of the 146 * verification that has been completed, ranging from 0 147 * to 100 (inclusive). 148 */ 149 public void onProgress(int progress); 150 } 151 152 /** @return the set of certs that can be used to sign an OTA package. */ 153 private static HashSet<X509Certificate> getTrustedCerts(File keystore) 154 throws IOException, GeneralSecurityException { 155 HashSet<X509Certificate> trusted = new HashSet<X509Certificate>(); 156 if (keystore == null) { 157 keystore = DEFAULT_KEYSTORE; 158 } 159 ZipFile zip = new ZipFile(keystore); 160 try { 161 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 162 Enumeration<? extends ZipEntry> entries = zip.entries(); 163 while (entries.hasMoreElements()) { 164 ZipEntry entry = entries.nextElement(); 165 InputStream is = zip.getInputStream(entry); 166 try { 167 trusted.add((X509Certificate) cf.generateCertificate(is)); 168 } finally { 169 is.close(); 170 } 171 } 172 } finally { 173 zip.close(); 174 } 175 return trusted; 176 } 177 178 /** 179 * Verify the cryptographic signature of a system update package 180 * before installing it. Note that the package is also verified 181 * separately by the installer once the device is rebooted into 182 * the recovery system. This function will return only if the 183 * package was successfully verified; otherwise it will throw an 184 * exception. 185 * 186 * Verification of a package can take significant time, so this 187 * function should not be called from a UI thread. Interrupting 188 * the thread while this function is in progress will result in a 189 * SecurityException being thrown (and the thread's interrupt flag 190 * will be cleared). 191 * 192 * @param packageFile the package to be verified 193 * @param listener an object to receive periodic progress 194 * updates as verification proceeds. May be null. 195 * @param deviceCertsZipFile the zip file of certificates whose 196 * public keys we will accept. Verification succeeds if the 197 * package is signed by the private key corresponding to any 198 * public key in this file. May be null to use the system default 199 * file (currently "/system/etc/security/otacerts.zip"). 200 * 201 * @throws IOException if there were any errors reading the 202 * package or certs files. 203 * @throws GeneralSecurityException if verification failed 204 */ 205 public static void verifyPackage(File packageFile, 206 ProgressListener listener, 207 File deviceCertsZipFile) 208 throws IOException, GeneralSecurityException { 209 final long fileLen = packageFile.length(); 210 211 final RandomAccessFile raf = new RandomAccessFile(packageFile, "r"); 212 try { 213 final long startTimeMillis = System.currentTimeMillis(); 214 if (listener != null) { 215 listener.onProgress(0); 216 } 217 218 raf.seek(fileLen - 6); 219 byte[] footer = new byte[6]; 220 raf.readFully(footer); 221 222 if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) { 223 throw new SignatureException("no signature in file (no footer)"); 224 } 225 226 final int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8); 227 final int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8); 228 229 byte[] eocd = new byte[commentSize + 22]; 230 raf.seek(fileLen - (commentSize + 22)); 231 raf.readFully(eocd); 232 233 // Check that we have found the start of the 234 // end-of-central-directory record. 235 if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b || 236 eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) { 237 throw new SignatureException("no signature in file (bad footer)"); 238 } 239 240 for (int i = 4; i < eocd.length-3; ++i) { 241 if (eocd[i ] == (byte)0x50 && eocd[i+1] == (byte)0x4b && 242 eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) { 243 throw new SignatureException("EOCD marker found after start of EOCD"); 244 } 245 } 246 247 // Parse the signature 248 PKCS7 block = 249 new PKCS7(new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart)); 250 251 // Take the first certificate from the signature (packages 252 // should contain only one). 253 X509Certificate[] certificates = block.getCertificates(); 254 if (certificates == null || certificates.length == 0) { 255 throw new SignatureException("signature contains no certificates"); 256 } 257 X509Certificate cert = certificates[0]; 258 PublicKey signatureKey = cert.getPublicKey(); 259 260 SignerInfo[] signerInfos = block.getSignerInfos(); 261 if (signerInfos == null || signerInfos.length == 0) { 262 throw new SignatureException("signature contains no signedData"); 263 } 264 SignerInfo signerInfo = signerInfos[0]; 265 266 // Check that the public key of the certificate contained 267 // in the package equals one of our trusted public keys. 268 boolean verified = false; 269 HashSet<X509Certificate> trusted = getTrustedCerts( 270 deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile); 271 for (X509Certificate c : trusted) { 272 if (c.getPublicKey().equals(signatureKey)) { 273 verified = true; 274 break; 275 } 276 } 277 if (!verified) { 278 throw new SignatureException("signature doesn't match any trusted key"); 279 } 280 281 // The signature cert matches a trusted key. Now verify that 282 // the digest in the cert matches the actual file data. 283 raf.seek(0); 284 final ProgressListener listenerForInner = listener; 285 SignerInfo verifyResult = block.verify(signerInfo, new InputStream() { 286 // The signature covers all of the OTA package except the 287 // archive comment and its 2-byte length. 288 long toRead = fileLen - commentSize - 2; 289 long soFar = 0; 290 291 int lastPercent = 0; 292 long lastPublishTime = startTimeMillis; 293 294 @Override 295 public int read() throws IOException { 296 throw new UnsupportedOperationException(); 297 } 298 299 @Override 300 public int read(byte[] b, int off, int len) throws IOException { 301 if (soFar >= toRead) { 302 return -1; 303 } 304 if (Thread.currentThread().isInterrupted()) { 305 return -1; 306 } 307 308 int size = len; 309 if (soFar + size > toRead) { 310 size = (int)(toRead - soFar); 311 } 312 int read = raf.read(b, off, size); 313 soFar += read; 314 315 if (listenerForInner != null) { 316 long now = System.currentTimeMillis(); 317 int p = (int)(soFar * 100 / toRead); 318 if (p > lastPercent && 319 now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) { 320 lastPercent = p; 321 lastPublishTime = now; 322 listenerForInner.onProgress(lastPercent); 323 } 324 } 325 326 return read; 327 } 328 }); 329 330 final boolean interrupted = Thread.interrupted(); 331 if (listener != null) { 332 listener.onProgress(100); 333 } 334 335 if (interrupted) { 336 throw new SignatureException("verification was interrupted"); 337 } 338 339 if (verifyResult == null) { 340 throw new SignatureException("signature digest verification failed"); 341 } 342 } finally { 343 raf.close(); 344 } 345 346 // Additionally verify the package compatibility. 347 if (!readAndVerifyPackageCompatibilityEntry(packageFile)) { 348 throw new SignatureException("package compatibility verification failed"); 349 } 350 } 351 352 /** 353 * Verifies the compatibility entry from an {@link InputStream}. 354 * 355 * @return the verification result. 356 */ 357 private static boolean verifyPackageCompatibility(InputStream inputStream) throws IOException { 358 ArrayList<String> list = new ArrayList<>(); 359 ZipInputStream zis = new ZipInputStream(inputStream); 360 ZipEntry entry; 361 while ((entry = zis.getNextEntry()) != null) { 362 long entrySize = entry.getSize(); 363 if (entrySize > Integer.MAX_VALUE || entrySize < 0) { 364 throw new IOException( 365 "invalid entry size (" + entrySize + ") in the compatibility file"); 366 } 367 byte[] bytes = new byte[(int) entrySize]; 368 Streams.readFully(zis, bytes); 369 list.add(new String(bytes, UTF_8)); 370 } 371 if (list.isEmpty()) { 372 throw new IOException("no entries found in the compatibility file"); 373 } 374 return (VintfObject.verify(list.toArray(new String[list.size()])) == 0); 375 } 376 377 /** 378 * Reads and verifies the compatibility entry in an OTA zip package. The compatibility entry is 379 * a zip file (inside the OTA package zip). 380 * 381 * @return {@code true} if the entry doesn't exist or verification passes. 382 */ 383 private static boolean readAndVerifyPackageCompatibilityEntry(File packageFile) 384 throws IOException { 385 try (ZipFile zip = new ZipFile(packageFile)) { 386 ZipEntry entry = zip.getEntry("compatibility.zip"); 387 if (entry == null) { 388 return true; 389 } 390 InputStream inputStream = zip.getInputStream(entry); 391 return verifyPackageCompatibility(inputStream); 392 } 393 } 394 395 /** 396 * Verifies the package compatibility info against the current system. 397 * 398 * @param compatibilityFile the {@link File} that contains the package compatibility info. 399 * @throws IOException if there were any errors reading the compatibility file. 400 * @return the compatibility verification result. 401 * 402 * {@hide} 403 */ 404 @SystemApi 405 @SuppressLint("Doclava125") 406 public static boolean verifyPackageCompatibility(File compatibilityFile) throws IOException { 407 try (InputStream inputStream = new FileInputStream(compatibilityFile)) { 408 return verifyPackageCompatibility(inputStream); 409 } 410 } 411 412 /** 413 * Process a given package with uncrypt. No-op if the package is not on the 414 * /data partition. 415 * 416 * @param Context the Context to use 417 * @param packageFile the package to be processed 418 * @param listener an object to receive periodic progress updates as 419 * processing proceeds. May be null. 420 * @param handler the Handler upon which the callbacks will be 421 * executed. 422 * 423 * @throws IOException if there were any errors processing the package file. 424 * 425 * @hide 426 */ 427 @SystemApi 428 @RequiresPermission(android.Manifest.permission.RECOVERY) 429 public static void processPackage(Context context, 430 File packageFile, 431 final ProgressListener listener, 432 final Handler handler) 433 throws IOException { 434 String filename = packageFile.getCanonicalPath(); 435 if (!filename.startsWith("/data/")) { 436 return; 437 } 438 439 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); 440 IRecoverySystemProgressListener progressListener = null; 441 if (listener != null) { 442 final Handler progressHandler; 443 if (handler != null) { 444 progressHandler = handler; 445 } else { 446 progressHandler = new Handler(context.getMainLooper()); 447 } 448 progressListener = new IRecoverySystemProgressListener.Stub() { 449 int lastProgress = 0; 450 long lastPublishTime = System.currentTimeMillis(); 451 452 @Override 453 public void onProgress(final int progress) { 454 final long now = System.currentTimeMillis(); 455 progressHandler.post(new Runnable() { 456 @Override 457 public void run() { 458 if (progress > lastProgress && 459 now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) { 460 lastProgress = progress; 461 lastPublishTime = now; 462 listener.onProgress(progress); 463 } 464 } 465 }); 466 } 467 }; 468 } 469 470 if (!rs.uncrypt(filename, progressListener)) { 471 throw new IOException("process package failed"); 472 } 473 } 474 475 /** 476 * Process a given package with uncrypt. No-op if the package is not on the 477 * /data partition. 478 * 479 * @param Context the Context to use 480 * @param packageFile the package to be processed 481 * @param listener an object to receive periodic progress updates as 482 * processing proceeds. May be null. 483 * 484 * @throws IOException if there were any errors processing the package file. 485 * 486 * @hide 487 */ 488 @SystemApi 489 @RequiresPermission(android.Manifest.permission.RECOVERY) 490 public static void processPackage(Context context, 491 File packageFile, 492 final ProgressListener listener) 493 throws IOException { 494 processPackage(context, packageFile, listener, null); 495 } 496 497 /** 498 * Reboots the device in order to install the given update 499 * package. 500 * Requires the {@link android.Manifest.permission#REBOOT} permission. 501 * 502 * @param context the Context to use 503 * @param packageFile the update package to install. Must be on 504 * a partition mountable by recovery. (The set of partitions 505 * known to recovery may vary from device to device. Generally, 506 * /cache and /data are safe.) 507 * 508 * @throws IOException if writing the recovery command file 509 * fails, or if the reboot itself fails. 510 */ 511 @RequiresPermission(android.Manifest.permission.RECOVERY) 512 public static void installPackage(Context context, File packageFile) 513 throws IOException { 514 installPackage(context, packageFile, false); 515 } 516 517 /** 518 * If the package hasn't been processed (i.e. uncrypt'd), set up 519 * UNCRYPT_PACKAGE_FILE and delete BLOCK_MAP_FILE to trigger uncrypt during the 520 * reboot. 521 * 522 * @param context the Context to use 523 * @param packageFile the update package to install. Must be on a 524 * partition mountable by recovery. 525 * @param processed if the package has been processed (uncrypt'd). 526 * 527 * @throws IOException if writing the recovery command file fails, or if 528 * the reboot itself fails. 529 * 530 * @hide 531 */ 532 @SystemApi 533 @RequiresPermission(android.Manifest.permission.RECOVERY) 534 public static void installPackage(Context context, File packageFile, boolean processed) 535 throws IOException { 536 synchronized (sRequestLock) { 537 LOG_FILE.delete(); 538 // Must delete the file in case it was created by system server. 539 UNCRYPT_PACKAGE_FILE.delete(); 540 541 String filename = packageFile.getCanonicalPath(); 542 Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!"); 543 544 // If the package name ends with "_s.zip", it's a security update. 545 boolean securityUpdate = filename.endsWith("_s.zip"); 546 547 // If the package is on the /data partition, the package needs to 548 // be processed (i.e. uncrypt'd). The caller specifies if that has 549 // been done in 'processed' parameter. 550 if (filename.startsWith("/data/")) { 551 if (processed) { 552 if (!BLOCK_MAP_FILE.exists()) { 553 Log.e(TAG, "Package claimed to have been processed but failed to find " 554 + "the block map file."); 555 throw new IOException("Failed to find block map file"); 556 } 557 } else { 558 FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE); 559 try { 560 uncryptFile.write(filename + "\n"); 561 } finally { 562 uncryptFile.close(); 563 } 564 // UNCRYPT_PACKAGE_FILE needs to be readable and writable 565 // by system server. 566 if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false) 567 || !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) { 568 Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE); 569 } 570 571 BLOCK_MAP_FILE.delete(); 572 } 573 574 // If the package is on the /data partition, use the block map 575 // file as the package name instead. 576 filename = "@/cache/recovery/block.map"; 577 } 578 579 final String filenameArg = "--update_package=" + filename + "\n"; 580 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n"; 581 final String securityArg = "--security\n"; 582 583 String command = filenameArg + localeArg; 584 if (securityUpdate) { 585 command += securityArg; 586 } 587 588 RecoverySystem rs = (RecoverySystem) context.getSystemService( 589 Context.RECOVERY_SERVICE); 590 if (!rs.setupBcb(command)) { 591 throw new IOException("Setup BCB failed"); 592 } 593 594 // Having set up the BCB (bootloader control block), go ahead and reboot 595 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 596 String reason = PowerManager.REBOOT_RECOVERY_UPDATE; 597 598 // On TV, reboot quiescently if the screen is off 599 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { 600 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 601 if (wm.getDefaultDisplay().getState() != Display.STATE_ON) { 602 reason += ",quiescent"; 603 } 604 } 605 pm.reboot(reason); 606 607 throw new IOException("Reboot failed (no permissions?)"); 608 } 609 } 610 611 /** 612 * Schedule to install the given package on next boot. The caller needs to 613 * ensure that the package must have been processed (uncrypt'd) if needed. 614 * It sets up the command in BCB (bootloader control block), which will 615 * be read by the bootloader and the recovery image. 616 * 617 * @param Context the Context to use. 618 * @param packageFile the package to be installed. 619 * 620 * @throws IOException if there were any errors setting up the BCB. 621 * 622 * @hide 623 */ 624 @SystemApi 625 @RequiresPermission(android.Manifest.permission.RECOVERY) 626 public static void scheduleUpdateOnBoot(Context context, File packageFile) 627 throws IOException { 628 String filename = packageFile.getCanonicalPath(); 629 boolean securityUpdate = filename.endsWith("_s.zip"); 630 631 // If the package is on the /data partition, use the block map file as 632 // the package name instead. 633 if (filename.startsWith("/data/")) { 634 filename = "@/cache/recovery/block.map"; 635 } 636 637 final String filenameArg = "--update_package=" + filename + "\n"; 638 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n"; 639 final String securityArg = "--security\n"; 640 641 String command = filenameArg + localeArg; 642 if (securityUpdate) { 643 command += securityArg; 644 } 645 646 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); 647 if (!rs.setupBcb(command)) { 648 throw new IOException("schedule update on boot failed"); 649 } 650 } 651 652 /** 653 * Cancel any scheduled update by clearing up the BCB (bootloader control 654 * block). 655 * 656 * @param Context the Context to use. 657 * 658 * @throws IOException if there were any errors clearing up the BCB. 659 * 660 * @hide 661 */ 662 @SystemApi 663 @RequiresPermission(android.Manifest.permission.RECOVERY) 664 public static void cancelScheduledUpdate(Context context) 665 throws IOException { 666 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); 667 if (!rs.clearBcb()) { 668 throw new IOException("cancel scheduled update failed"); 669 } 670 } 671 672 /** 673 * Reboots the device and wipes the user data and cache 674 * partitions. This is sometimes called a "factory reset", which 675 * is something of a misnomer because the system partition is not 676 * restored to its factory state. Requires the 677 * {@link android.Manifest.permission#REBOOT} permission. 678 * 679 * @param context the Context to use 680 * 681 * @throws IOException if writing the recovery command file 682 * fails, or if the reboot itself fails. 683 * @throws SecurityException if the current user is not allowed to wipe data. 684 */ 685 public static void rebootWipeUserData(Context context) throws IOException { 686 rebootWipeUserData(context, false /* shutdown */, context.getPackageName(), 687 false /* force */, false /* wipeEuicc */); 688 } 689 690 /** {@hide} */ 691 public static void rebootWipeUserData(Context context, String reason) throws IOException { 692 rebootWipeUserData(context, false /* shutdown */, reason, false /* force */, 693 false /* wipeEuicc */); 694 } 695 696 /** {@hide} */ 697 public static void rebootWipeUserData(Context context, boolean shutdown) 698 throws IOException { 699 rebootWipeUserData(context, shutdown, context.getPackageName(), false /* force */, 700 false /* wipeEuicc */); 701 } 702 703 /** {@hide} */ 704 public static void rebootWipeUserData(Context context, boolean shutdown, String reason, 705 boolean force) throws IOException { 706 rebootWipeUserData(context, shutdown, reason, force, false /* wipeEuicc */); 707 } 708 709 /** 710 * Reboots the device and wipes the user data and cache 711 * partitions. This is sometimes called a "factory reset", which 712 * is something of a misnomer because the system partition is not 713 * restored to its factory state. Requires the 714 * {@link android.Manifest.permission#REBOOT} permission. 715 * 716 * @param context the Context to use 717 * @param shutdown if true, the device will be powered down after 718 * the wipe completes, rather than being rebooted 719 * back to the regular system. 720 * @param reason the reason for the wipe that is visible in the logs 721 * @param force whether the {@link UserManager.DISALLOW_FACTORY_RESET} user restriction 722 * should be ignored 723 * @param wipeEuicc whether wipe the euicc data 724 * 725 * @throws IOException if writing the recovery command file 726 * fails, or if the reboot itself fails. 727 * @throws SecurityException if the current user is not allowed to wipe data. 728 * 729 * @hide 730 */ 731 public static void rebootWipeUserData(Context context, boolean shutdown, String reason, 732 boolean force, boolean wipeEuicc) throws IOException { 733 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 734 if (!force && um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) { 735 throw new SecurityException("Wiping data is not allowed for this user."); 736 } 737 final ConditionVariable condition = new ConditionVariable(); 738 739 Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION"); 740 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND 741 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 742 context.sendOrderedBroadcastAsUser(intent, UserHandle.SYSTEM, 743 android.Manifest.permission.MASTER_CLEAR, 744 new BroadcastReceiver() { 745 @Override 746 public void onReceive(Context context, Intent intent) { 747 condition.open(); 748 } 749 }, null, 0, null, null); 750 751 // Block until the ordered broadcast has completed. 752 condition.block(); 753 754 wipeEuiccData(context, wipeEuicc); 755 756 String shutdownArg = null; 757 if (shutdown) { 758 shutdownArg = "--shutdown_after"; 759 } 760 761 String reasonArg = null; 762 if (!TextUtils.isEmpty(reason)) { 763 reasonArg = "--reason=" + sanitizeArg(reason); 764 } 765 766 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ; 767 bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg); 768 } 769 770 private static void wipeEuiccData(Context context, final boolean isWipeEuicc) { 771 ContentResolver cr = context.getContentResolver(); 772 if (Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) == 0) { 773 // If the eUICC isn't provisioned, there's no reason to either wipe or retain profiles, 774 // as there's nothing to wipe nor retain. 775 Log.d(TAG, "Skipping eUICC wipe/retain as it is not provisioned"); 776 return; 777 } 778 779 EuiccManager euiccManager = (EuiccManager) context.getSystemService( 780 Context.EUICC_SERVICE); 781 if (euiccManager != null && euiccManager.isEnabled()) { 782 CountDownLatch euiccFactoryResetLatch = new CountDownLatch(1); 783 784 BroadcastReceiver euiccWipeFinishReceiver = new BroadcastReceiver() { 785 @Override 786 public void onReceive(Context context, Intent intent) { 787 if (ACTION_EUICC_FACTORY_RESET.equals(intent.getAction())) { 788 if (getResultCode() != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) { 789 int detailedCode = intent.getIntExtra( 790 EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0); 791 if (isWipeEuicc) { 792 Log.e(TAG, "Error wiping euicc data, Detailed code = " 793 + detailedCode); 794 } else { 795 Log.e(TAG, "Error retaining euicc data, Detailed code = " 796 + detailedCode); 797 } 798 } else { 799 if (isWipeEuicc) { 800 Log.d(TAG, "Successfully wiped euicc data."); 801 } else { 802 Log.d(TAG, "Successfully retained euicc data."); 803 } 804 } 805 euiccFactoryResetLatch.countDown(); 806 } 807 } 808 }; 809 810 Intent intent = new Intent(ACTION_EUICC_FACTORY_RESET); 811 intent.setPackage("android"); 812 PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser( 813 context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, UserHandle.SYSTEM); 814 IntentFilter filterConsent = new IntentFilter(); 815 filterConsent.addAction(ACTION_EUICC_FACTORY_RESET); 816 HandlerThread euiccHandlerThread = new HandlerThread("euiccWipeFinishReceiverThread"); 817 euiccHandlerThread.start(); 818 Handler euiccHandler = new Handler(euiccHandlerThread.getLooper()); 819 context.getApplicationContext() 820 .registerReceiver(euiccWipeFinishReceiver, filterConsent, null, euiccHandler); 821 if (isWipeEuicc) { 822 euiccManager.eraseSubscriptions(callbackIntent); 823 } else { 824 euiccManager.retainSubscriptionsForFactoryReset(callbackIntent); 825 } 826 try { 827 long waitingTimeMillis = Settings.Global.getLong( 828 context.getContentResolver(), 829 Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS, 830 DEFAULT_EUICC_FACTORY_RESET_TIMEOUT_MILLIS); 831 if (waitingTimeMillis < MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS) { 832 waitingTimeMillis = MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS; 833 } else if (waitingTimeMillis > MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS) { 834 waitingTimeMillis = MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS; 835 } 836 if (!euiccFactoryResetLatch.await(waitingTimeMillis, TimeUnit.MILLISECONDS)) { 837 if (isWipeEuicc) { 838 Log.e(TAG, "Timeout wiping eUICC data."); 839 } else { 840 Log.e(TAG, "Timeout retaining eUICC data."); 841 } 842 } 843 context.getApplicationContext().unregisterReceiver(euiccWipeFinishReceiver); 844 } catch (InterruptedException e) { 845 Thread.currentThread().interrupt(); 846 if (isWipeEuicc) { 847 Log.e(TAG, "Wiping eUICC data interrupted", e); 848 } else { 849 Log.e(TAG, "Retaining eUICC data interrupted", e); 850 } 851 } 852 } 853 } 854 855 /** {@hide} */ 856 public static void rebootPromptAndWipeUserData(Context context, String reason) 857 throws IOException { 858 String reasonArg = null; 859 if (!TextUtils.isEmpty(reason)) { 860 reasonArg = "--reason=" + sanitizeArg(reason); 861 } 862 863 final String localeArg = "--locale=" + Locale.getDefault().toString(); 864 bootCommand(context, null, "--prompt_and_wipe_data", reasonArg, localeArg); 865 } 866 867 /** 868 * Reboot into the recovery system to wipe the /cache partition. 869 * @throws IOException if something goes wrong. 870 */ 871 public static void rebootWipeCache(Context context) throws IOException { 872 rebootWipeCache(context, context.getPackageName()); 873 } 874 875 /** {@hide} */ 876 public static void rebootWipeCache(Context context, String reason) throws IOException { 877 String reasonArg = null; 878 if (!TextUtils.isEmpty(reason)) { 879 reasonArg = "--reason=" + sanitizeArg(reason); 880 } 881 882 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ; 883 bootCommand(context, "--wipe_cache", reasonArg, localeArg); 884 } 885 886 /** 887 * Reboot into recovery and wipe the A/B device. 888 * 889 * @param Context the Context to use. 890 * @param packageFile the wipe package to be applied. 891 * @param reason the reason to wipe. 892 * 893 * @throws IOException if something goes wrong. 894 * 895 * @hide 896 */ 897 @SystemApi 898 @RequiresPermission(allOf = { 899 android.Manifest.permission.RECOVERY, 900 android.Manifest.permission.REBOOT 901 }) 902 public static void rebootWipeAb(Context context, File packageFile, String reason) 903 throws IOException { 904 String reasonArg = null; 905 if (!TextUtils.isEmpty(reason)) { 906 reasonArg = "--reason=" + sanitizeArg(reason); 907 } 908 909 final String filename = packageFile.getCanonicalPath(); 910 final String filenameArg = "--wipe_package=" + filename; 911 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ; 912 bootCommand(context, "--wipe_ab", filenameArg, reasonArg, localeArg); 913 } 914 915 /** 916 * Reboot into the recovery system with the supplied argument. 917 * @param args to pass to the recovery utility. 918 * @throws IOException if something goes wrong. 919 */ 920 private static void bootCommand(Context context, String... args) throws IOException { 921 LOG_FILE.delete(); 922 923 StringBuilder command = new StringBuilder(); 924 for (String arg : args) { 925 if (!TextUtils.isEmpty(arg)) { 926 command.append(arg); 927 command.append("\n"); 928 } 929 } 930 931 // Write the command into BCB (bootloader control block) and boot from 932 // there. Will not return unless failed. 933 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); 934 rs.rebootRecoveryWithCommand(command.toString()); 935 936 throw new IOException("Reboot failed (no permissions?)"); 937 } 938 939 // Read last_install; then report time (in seconds) and I/O (in MiB) for 940 // this update to tron. 941 // Only report on the reboots immediately after an OTA update. 942 private static void parseLastInstallLog(Context context) { 943 try (BufferedReader in = new BufferedReader(new FileReader(LAST_INSTALL_FILE))) { 944 String line = null; 945 int bytesWrittenInMiB = -1, bytesStashedInMiB = -1; 946 int timeTotal = -1; 947 int uncryptTime = -1; 948 int sourceVersion = -1; 949 int temperatureStart = -1; 950 int temperatureEnd = -1; 951 int temperatureMax = -1; 952 int errorCode = -1; 953 int causeCode = -1; 954 955 while ((line = in.readLine()) != null) { 956 // Here is an example of lines in last_install: 957 // ... 958 // time_total: 101 959 // bytes_written_vendor: 51074 960 // bytes_stashed_vendor: 200 961 int numIndex = line.indexOf(':'); 962 if (numIndex == -1 || numIndex + 1 >= line.length()) { 963 continue; 964 } 965 String numString = line.substring(numIndex + 1).trim(); 966 long parsedNum; 967 try { 968 parsedNum = Long.parseLong(numString); 969 } catch (NumberFormatException ignored) { 970 Log.e(TAG, "Failed to parse numbers in " + line); 971 continue; 972 } 973 974 final int MiB = 1024 * 1024; 975 int scaled; 976 try { 977 if (line.startsWith("bytes")) { 978 scaled = Math.toIntExact(parsedNum / MiB); 979 } else { 980 scaled = Math.toIntExact(parsedNum); 981 } 982 } catch (ArithmeticException ignored) { 983 Log.e(TAG, "Number overflows in " + line); 984 continue; 985 } 986 987 if (line.startsWith("time")) { 988 timeTotal = scaled; 989 } else if (line.startsWith("uncrypt_time")) { 990 uncryptTime = scaled; 991 } else if (line.startsWith("source_build")) { 992 sourceVersion = scaled; 993 } else if (line.startsWith("bytes_written")) { 994 bytesWrittenInMiB = (bytesWrittenInMiB == -1) ? scaled : 995 bytesWrittenInMiB + scaled; 996 } else if (line.startsWith("bytes_stashed")) { 997 bytesStashedInMiB = (bytesStashedInMiB == -1) ? scaled : 998 bytesStashedInMiB + scaled; 999 } else if (line.startsWith("temperature_start")) { 1000 temperatureStart = scaled; 1001 } else if (line.startsWith("temperature_end")) { 1002 temperatureEnd = scaled; 1003 } else if (line.startsWith("temperature_max")) { 1004 temperatureMax = scaled; 1005 } else if (line.startsWith("error")) { 1006 errorCode = scaled; 1007 } else if (line.startsWith("cause")) { 1008 causeCode = scaled; 1009 } 1010 } 1011 1012 // Don't report data to tron if corresponding entry isn't found in last_install. 1013 if (timeTotal != -1) { 1014 MetricsLogger.histogram(context, "ota_time_total", timeTotal); 1015 } 1016 if (uncryptTime != -1) { 1017 MetricsLogger.histogram(context, "ota_uncrypt_time", uncryptTime); 1018 } 1019 if (sourceVersion != -1) { 1020 MetricsLogger.histogram(context, "ota_source_version", sourceVersion); 1021 } 1022 if (bytesWrittenInMiB != -1) { 1023 MetricsLogger.histogram(context, "ota_written_in_MiBs", bytesWrittenInMiB); 1024 } 1025 if (bytesStashedInMiB != -1) { 1026 MetricsLogger.histogram(context, "ota_stashed_in_MiBs", bytesStashedInMiB); 1027 } 1028 if (temperatureStart != -1) { 1029 MetricsLogger.histogram(context, "ota_temperature_start", temperatureStart); 1030 } 1031 if (temperatureEnd != -1) { 1032 MetricsLogger.histogram(context, "ota_temperature_end", temperatureEnd); 1033 } 1034 if (temperatureMax != -1) { 1035 MetricsLogger.histogram(context, "ota_temperature_max", temperatureMax); 1036 } 1037 if (errorCode != -1) { 1038 MetricsLogger.histogram(context, "ota_non_ab_error_code", errorCode); 1039 } 1040 if (causeCode != -1) { 1041 MetricsLogger.histogram(context, "ota_non_ab_cause_code", causeCode); 1042 } 1043 1044 } catch (IOException e) { 1045 Log.e(TAG, "Failed to read lines in last_install", e); 1046 } 1047 } 1048 1049 /** 1050 * Called after booting to process and remove recovery-related files. 1051 * @return the log file from recovery, or null if none was found. 1052 * 1053 * @hide 1054 */ 1055 public static String handleAftermath(Context context) { 1056 // Record the tail of the LOG_FILE 1057 String log = null; 1058 try { 1059 log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n"); 1060 } catch (FileNotFoundException e) { 1061 Log.i(TAG, "No recovery log file"); 1062 } catch (IOException e) { 1063 Log.e(TAG, "Error reading recovery log", e); 1064 } 1065 1066 if (log != null) { 1067 parseLastInstallLog(context); 1068 } 1069 1070 // Only remove the OTA package if it's partially processed (uncrypt'd). 1071 boolean reservePackage = BLOCK_MAP_FILE.exists(); 1072 if (!reservePackage && UNCRYPT_PACKAGE_FILE.exists()) { 1073 String filename = null; 1074 try { 1075 filename = FileUtils.readTextFile(UNCRYPT_PACKAGE_FILE, 0, null); 1076 } catch (IOException e) { 1077 Log.e(TAG, "Error reading uncrypt file", e); 1078 } 1079 1080 // Remove the OTA package on /data that has been (possibly 1081 // partially) processed. (Bug: 24973532) 1082 if (filename != null && filename.startsWith("/data")) { 1083 if (UNCRYPT_PACKAGE_FILE.delete()) { 1084 Log.i(TAG, "Deleted: " + filename); 1085 } else { 1086 Log.e(TAG, "Can't delete: " + filename); 1087 } 1088 } 1089 } 1090 1091 // We keep the update logs (beginning with LAST_PREFIX), and optionally 1092 // the block map file (BLOCK_MAP_FILE) for a package. BLOCK_MAP_FILE 1093 // will be created at the end of a successful uncrypt. If seeing this 1094 // file, we keep the block map file and the file that contains the 1095 // package name (UNCRYPT_PACKAGE_FILE). This is to reduce the work for 1096 // GmsCore to avoid re-downloading everything again. 1097 String[] names = RECOVERY_DIR.list(); 1098 for (int i = 0; names != null && i < names.length; i++) { 1099 if (names[i].startsWith(LAST_PREFIX)) continue; 1100 if (reservePackage && names[i].equals(BLOCK_MAP_FILE.getName())) continue; 1101 if (reservePackage && names[i].equals(UNCRYPT_PACKAGE_FILE.getName())) continue; 1102 1103 recursiveDelete(new File(RECOVERY_DIR, names[i])); 1104 } 1105 1106 return log; 1107 } 1108 1109 /** 1110 * Internally, delete a given file or directory recursively. 1111 */ 1112 private static void recursiveDelete(File name) { 1113 if (name.isDirectory()) { 1114 String[] files = name.list(); 1115 for (int i = 0; files != null && i < files.length; i++) { 1116 File f = new File(name, files[i]); 1117 recursiveDelete(f); 1118 } 1119 } 1120 1121 if (!name.delete()) { 1122 Log.e(TAG, "Can't delete: " + name); 1123 } else { 1124 Log.i(TAG, "Deleted: " + name); 1125 } 1126 } 1127 1128 /** 1129 * Talks to RecoverySystemService via Binder to trigger uncrypt. 1130 */ 1131 private boolean uncrypt(String packageFile, IRecoverySystemProgressListener listener) { 1132 try { 1133 return mService.uncrypt(packageFile, listener); 1134 } catch (RemoteException unused) { 1135 } 1136 return false; 1137 } 1138 1139 /** 1140 * Talks to RecoverySystemService via Binder to set up the BCB. 1141 */ 1142 private boolean setupBcb(String command) { 1143 try { 1144 return mService.setupBcb(command); 1145 } catch (RemoteException unused) { 1146 } 1147 return false; 1148 } 1149 1150 /** 1151 * Talks to RecoverySystemService via Binder to clear up the BCB. 1152 */ 1153 private boolean clearBcb() { 1154 try { 1155 return mService.clearBcb(); 1156 } catch (RemoteException unused) { 1157 } 1158 return false; 1159 } 1160 1161 /** 1162 * Talks to RecoverySystemService via Binder to set up the BCB command and 1163 * reboot into recovery accordingly. 1164 */ 1165 private void rebootRecoveryWithCommand(String command) { 1166 try { 1167 mService.rebootRecoveryWithCommand(command); 1168 } catch (RemoteException ignored) { 1169 } 1170 } 1171 1172 /** 1173 * Internally, recovery treats each line of the command file as a separate 1174 * argv, so we only need to protect against newlines and nulls. 1175 */ 1176 private static String sanitizeArg(String arg) { 1177 arg = arg.replace('\0', '?'); 1178 arg = arg.replace('\n', '?'); 1179 return arg; 1180 } 1181 1182 1183 /** 1184 * @removed Was previously made visible by accident. 1185 */ 1186 public RecoverySystem() { 1187 mService = null; 1188 } 1189 1190 /** 1191 * @hide 1192 */ 1193 public RecoverySystem(IRecoverySystem service) { 1194 mService = service; 1195 } 1196 } 1197