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