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