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