RecoverySystem.java revision e8a403d57c8ea540f8287cdaee8b90f0cf9626a3
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 17package android.os; 18 19import android.annotation.SystemApi; 20import android.content.BroadcastReceiver; 21import android.content.Context; 22import android.content.Intent; 23import android.os.UserManager; 24import android.text.TextUtils; 25import android.util.Log; 26 27import java.io.ByteArrayInputStream; 28import java.io.File; 29import java.io.FileNotFoundException; 30import java.io.FileWriter; 31import java.io.IOException; 32import java.io.InputStream; 33import java.io.RandomAccessFile; 34import java.security.GeneralSecurityException; 35import java.security.PublicKey; 36import java.security.Signature; 37import java.security.SignatureException; 38import java.security.cert.CertificateFactory; 39import java.security.cert.X509Certificate; 40import java.util.Enumeration; 41import java.util.HashSet; 42import java.util.Iterator; 43import java.util.List; 44import java.util.Locale; 45import java.util.zip.ZipEntry; 46import java.util.zip.ZipFile; 47 48import sun.security.pkcs.PKCS7; 49import sun.security.pkcs.SignerInfo; 50 51/** 52 * RecoverySystem contains methods for interacting with the Android 53 * recovery system (the separate partition that can be used to install 54 * system updates, wipe user data, etc.) 55 */ 56public class RecoverySystem { 57 private static final String TAG = "RecoverySystem"; 58 59 /** 60 * Default location of zip file containing public keys (X509 61 * certs) authorized to sign OTA updates. 62 */ 63 private static final File DEFAULT_KEYSTORE = 64 new File("/system/etc/security/otacerts.zip"); 65 66 /** Send progress to listeners no more often than this (in ms). */ 67 private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500; 68 69 /** Used to communicate with recovery. See bootable/recovery/recovery.cpp. */ 70 private static final File RECOVERY_DIR = new File("/cache/recovery"); 71 private static final File LOG_FILE = new File(RECOVERY_DIR, "log"); 72 private static final String LAST_PREFIX = "last_"; 73 74 /** 75 * The recovery image uses this file to identify the location (i.e. blocks) 76 * of an OTA package on the /data partition. The block map file is 77 * generated by uncrypt. 78 * 79 * @hide 80 */ 81 public static final File BLOCK_MAP_FILE = new File(RECOVERY_DIR, "block.map"); 82 83 /** 84 * UNCRYPT_PACKAGE_FILE stores the filename to be uncrypt'd, which will be 85 * read by uncrypt. 86 * 87 * @hide 88 */ 89 public static final File UNCRYPT_PACKAGE_FILE = new File(RECOVERY_DIR, "uncrypt_file"); 90 91 // Length limits for reading files. 92 private static final int LOG_FILE_MAX_LENGTH = 64 * 1024; 93 94 // Prevent concurrent execution of requests. 95 private static final Object sRequestLock = new Object(); 96 97 private final IRecoverySystem mService; 98 99 /** 100 * Interface definition for a callback to be invoked regularly as 101 * verification proceeds. 102 */ 103 public interface ProgressListener { 104 /** 105 * Called periodically as the verification progresses. 106 * 107 * @param progress the approximate percentage of the 108 * verification that has been completed, ranging from 0 109 * to 100 (inclusive). 110 */ 111 public void onProgress(int progress); 112 } 113 114 /** @return the set of certs that can be used to sign an OTA package. */ 115 private static HashSet<X509Certificate> getTrustedCerts(File keystore) 116 throws IOException, GeneralSecurityException { 117 HashSet<X509Certificate> trusted = new HashSet<X509Certificate>(); 118 if (keystore == null) { 119 keystore = DEFAULT_KEYSTORE; 120 } 121 ZipFile zip = new ZipFile(keystore); 122 try { 123 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 124 Enumeration<? extends ZipEntry> entries = zip.entries(); 125 while (entries.hasMoreElements()) { 126 ZipEntry entry = entries.nextElement(); 127 InputStream is = zip.getInputStream(entry); 128 try { 129 trusted.add((X509Certificate) cf.generateCertificate(is)); 130 } finally { 131 is.close(); 132 } 133 } 134 } finally { 135 zip.close(); 136 } 137 return trusted; 138 } 139 140 /** 141 * Verify the cryptographic signature of a system update package 142 * before installing it. Note that the package is also verified 143 * separately by the installer once the device is rebooted into 144 * the recovery system. This function will return only if the 145 * package was successfully verified; otherwise it will throw an 146 * exception. 147 * 148 * Verification of a package can take significant time, so this 149 * function should not be called from a UI thread. Interrupting 150 * the thread while this function is in progress will result in a 151 * SecurityException being thrown (and the thread's interrupt flag 152 * will be cleared). 153 * 154 * @param packageFile the package to be verified 155 * @param listener an object to receive periodic progress 156 * updates as verification proceeds. May be null. 157 * @param deviceCertsZipFile the zip file of certificates whose 158 * public keys we will accept. Verification succeeds if the 159 * package is signed by the private key corresponding to any 160 * public key in this file. May be null to use the system default 161 * file (currently "/system/etc/security/otacerts.zip"). 162 * 163 * @throws IOException if there were any errors reading the 164 * package or certs files. 165 * @throws GeneralSecurityException if verification failed 166 */ 167 public static void verifyPackage(File packageFile, 168 ProgressListener listener, 169 File deviceCertsZipFile) 170 throws IOException, GeneralSecurityException { 171 final long fileLen = packageFile.length(); 172 173 final RandomAccessFile raf = new RandomAccessFile(packageFile, "r"); 174 try { 175 final long startTimeMillis = System.currentTimeMillis(); 176 if (listener != null) { 177 listener.onProgress(0); 178 } 179 180 raf.seek(fileLen - 6); 181 byte[] footer = new byte[6]; 182 raf.readFully(footer); 183 184 if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) { 185 throw new SignatureException("no signature in file (no footer)"); 186 } 187 188 final int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8); 189 final int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8); 190 191 byte[] eocd = new byte[commentSize + 22]; 192 raf.seek(fileLen - (commentSize + 22)); 193 raf.readFully(eocd); 194 195 // Check that we have found the start of the 196 // end-of-central-directory record. 197 if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b || 198 eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) { 199 throw new SignatureException("no signature in file (bad footer)"); 200 } 201 202 for (int i = 4; i < eocd.length-3; ++i) { 203 if (eocd[i ] == (byte)0x50 && eocd[i+1] == (byte)0x4b && 204 eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) { 205 throw new SignatureException("EOCD marker found after start of EOCD"); 206 } 207 } 208 209 // Parse the signature 210 PKCS7 block = 211 new PKCS7(new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart)); 212 213 // Take the first certificate from the signature (packages 214 // should contain only one). 215 X509Certificate[] certificates = block.getCertificates(); 216 if (certificates == null || certificates.length == 0) { 217 throw new SignatureException("signature contains no certificates"); 218 } 219 X509Certificate cert = certificates[0]; 220 PublicKey signatureKey = cert.getPublicKey(); 221 222 SignerInfo[] signerInfos = block.getSignerInfos(); 223 if (signerInfos == null || signerInfos.length == 0) { 224 throw new SignatureException("signature contains no signedData"); 225 } 226 SignerInfo signerInfo = signerInfos[0]; 227 228 // Check that the public key of the certificate contained 229 // in the package equals one of our trusted public keys. 230 boolean verified = false; 231 HashSet<X509Certificate> trusted = getTrustedCerts( 232 deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile); 233 for (X509Certificate c : trusted) { 234 if (c.getPublicKey().equals(signatureKey)) { 235 verified = true; 236 break; 237 } 238 } 239 if (!verified) { 240 throw new SignatureException("signature doesn't match any trusted key"); 241 } 242 243 // The signature cert matches a trusted key. Now verify that 244 // the digest in the cert matches the actual file data. 245 raf.seek(0); 246 final ProgressListener listenerForInner = listener; 247 SignerInfo verifyResult = block.verify(signerInfo, new InputStream() { 248 // The signature covers all of the OTA package except the 249 // archive comment and its 2-byte length. 250 long toRead = fileLen - commentSize - 2; 251 long soFar = 0; 252 253 int lastPercent = 0; 254 long lastPublishTime = startTimeMillis; 255 256 @Override 257 public int read() throws IOException { 258 throw new UnsupportedOperationException(); 259 } 260 261 @Override 262 public int read(byte[] b, int off, int len) throws IOException { 263 if (soFar >= toRead) { 264 return -1; 265 } 266 if (Thread.currentThread().isInterrupted()) { 267 return -1; 268 } 269 270 int size = len; 271 if (soFar + size > toRead) { 272 size = (int)(toRead - soFar); 273 } 274 int read = raf.read(b, off, size); 275 soFar += read; 276 277 if (listenerForInner != null) { 278 long now = System.currentTimeMillis(); 279 int p = (int)(soFar * 100 / toRead); 280 if (p > lastPercent && 281 now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) { 282 lastPercent = p; 283 lastPublishTime = now; 284 listenerForInner.onProgress(lastPercent); 285 } 286 } 287 288 return read; 289 } 290 }); 291 292 final boolean interrupted = Thread.interrupted(); 293 if (listener != null) { 294 listener.onProgress(100); 295 } 296 297 if (interrupted) { 298 throw new SignatureException("verification was interrupted"); 299 } 300 301 if (verifyResult == null) { 302 throw new SignatureException("signature digest verification failed"); 303 } 304 } finally { 305 raf.close(); 306 } 307 } 308 309 /** 310 * Process a given package with uncrypt. No-op if the package is not on the 311 * /data partition. 312 * 313 * @param Context the Context to use 314 * @param packageFile the package to be processed 315 * @param listener an object to receive periodic progress updates as 316 * processing proceeds. May be null. 317 * @param handler the Handler upon which the callbacks will be 318 * executed. 319 * 320 * @throws IOException if there were any errors processing the package file. 321 * 322 * @hide 323 */ 324 @SystemApi 325 public static void processPackage(Context context, 326 File packageFile, 327 final ProgressListener listener, 328 final Handler handler) 329 throws IOException { 330 String filename = packageFile.getCanonicalPath(); 331 if (!filename.startsWith("/data/")) { 332 return; 333 } 334 335 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); 336 IRecoverySystemProgressListener progressListener = null; 337 if (listener != null) { 338 final Handler progressHandler; 339 if (handler != null) { 340 progressHandler = handler; 341 } else { 342 progressHandler = new Handler(context.getMainLooper()); 343 } 344 progressListener = new IRecoverySystemProgressListener.Stub() { 345 int lastProgress = 0; 346 long lastPublishTime = System.currentTimeMillis(); 347 348 @Override 349 public void onProgress(final int progress) { 350 final long now = System.currentTimeMillis(); 351 progressHandler.post(new Runnable() { 352 @Override 353 public void run() { 354 if (progress > lastProgress && 355 now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) { 356 lastProgress = progress; 357 lastPublishTime = now; 358 listener.onProgress(progress); 359 } 360 } 361 }); 362 } 363 }; 364 } 365 366 if (!rs.uncrypt(filename, progressListener)) { 367 throw new IOException("process package failed"); 368 } 369 } 370 371 /** 372 * Process a given package with uncrypt. No-op if the package is not on the 373 * /data partition. 374 * 375 * @param Context the Context to use 376 * @param packageFile the package to be processed 377 * @param listener an object to receive periodic progress updates as 378 * processing proceeds. May be null. 379 * 380 * @throws IOException if there were any errors processing the package file. 381 * 382 * @hide 383 */ 384 @SystemApi 385 public static void processPackage(Context context, 386 File packageFile, 387 final ProgressListener listener) 388 throws IOException { 389 processPackage(context, packageFile, listener, null); 390 } 391 392 /** 393 * Reboots the device in order to install the given update 394 * package. 395 * Requires the {@link android.Manifest.permission#REBOOT} permission. 396 * 397 * @param context the Context to use 398 * @param packageFile the update package to install. Must be on 399 * a partition mountable by recovery. (The set of partitions 400 * known to recovery may vary from device to device. Generally, 401 * /cache and /data are safe.) 402 * 403 * @throws IOException if writing the recovery command file 404 * fails, or if the reboot itself fails. 405 */ 406 public static void installPackage(Context context, File packageFile) 407 throws IOException { 408 installPackage(context, packageFile, false); 409 } 410 411 /** 412 * If the package hasn't been processed (i.e. uncrypt'd), set up 413 * UNCRYPT_PACKAGE_FILE and delete BLOCK_MAP_FILE to trigger uncrypt during the 414 * reboot. 415 * 416 * @param context the Context to use 417 * @param packageFile the update package to install. Must be on a 418 * partition mountable by recovery. 419 * @param processed if the package has been processed (uncrypt'd). 420 * 421 * @throws IOException if writing the recovery command file fails, or if 422 * the reboot itself fails. 423 * 424 * @hide 425 */ 426 @SystemApi 427 public static void installPackage(Context context, File packageFile, boolean processed) 428 throws IOException { 429 synchronized (sRequestLock) { 430 LOG_FILE.delete(); 431 // Must delete the file in case it was created by system server. 432 UNCRYPT_PACKAGE_FILE.delete(); 433 434 String filename = packageFile.getCanonicalPath(); 435 Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!"); 436 437 if (!processed && filename.startsWith("/data/")) { 438 FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE); 439 try { 440 uncryptFile.write(filename + "\n"); 441 } finally { 442 uncryptFile.close(); 443 } 444 // UNCRYPT_PACKAGE_FILE needs to be readable and writable by system server. 445 if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false) 446 || !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) { 447 Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE); 448 } 449 450 BLOCK_MAP_FILE.delete(); 451 } 452 453 // If the package is on the /data partition, use the block map file as 454 // the package name instead. 455 if (filename.startsWith("/data/")) { 456 filename = "@/cache/recovery/block.map"; 457 } 458 459 final String filenameArg = "--update_package=" + filename + "\n"; 460 final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n"; 461 final String command = filenameArg + localeArg; 462 463 RecoverySystem rs = (RecoverySystem) context.getSystemService( 464 Context.RECOVERY_SERVICE); 465 if (!rs.setupBcb(command)) { 466 throw new IOException("Setup BCB failed"); 467 } 468 469 // Having set up the BCB (bootloader control block), go ahead and reboot 470 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 471 pm.reboot(PowerManager.REBOOT_RECOVERY_UPDATE); 472 473 throw new IOException("Reboot failed (no permissions?)"); 474 } 475 } 476 477 /** 478 * Schedule to install the given package on next boot. The caller needs to 479 * ensure that the package must have been processed (uncrypt'd) if needed. 480 * It sets up the command in BCB (bootloader control block), which will 481 * be read by the bootloader and the recovery image. 482 * 483 * @param Context the Context to use. 484 * @param packageFile the package to be installed. 485 * 486 * @throws IOException if there were any errors setting up the BCB. 487 * 488 * @hide 489 */ 490 @SystemApi 491 public static void scheduleUpdateOnBoot(Context context, File packageFile) 492 throws IOException { 493 String filename = packageFile.getCanonicalPath(); 494 495 // If the package is on the /data partition, use the block map file as 496 // the package name instead. 497 if (filename.startsWith("/data/")) { 498 filename = "@/cache/recovery/block.map"; 499 } 500 501 final String filenameArg = "--update_package=" + filename + "\n"; 502 final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n"; 503 final String command = filenameArg + localeArg; 504 505 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); 506 if (!rs.setupBcb(command)) { 507 throw new IOException("schedule update on boot failed"); 508 } 509 } 510 511 /** 512 * Cancel any scheduled update by clearing up the BCB (bootloader control 513 * block). 514 * 515 * @param Context the Context to use. 516 * 517 * @throws IOException if there were any errors clearing up the BCB. 518 * 519 * @hide 520 */ 521 @SystemApi 522 public static void cancelScheduledUpdate(Context context) 523 throws IOException { 524 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); 525 if (!rs.clearBcb()) { 526 throw new IOException("cancel scheduled update failed"); 527 } 528 } 529 530 /** 531 * Reboots the device and wipes the user data and cache 532 * partitions. This is sometimes called a "factory reset", which 533 * is something of a misnomer because the system partition is not 534 * restored to its factory state. Requires the 535 * {@link android.Manifest.permission#REBOOT} permission. 536 * 537 * @param context the Context to use 538 * 539 * @throws IOException if writing the recovery command file 540 * fails, or if the reboot itself fails. 541 * @throws SecurityException if the current user is not allowed to wipe data. 542 */ 543 public static void rebootWipeUserData(Context context) throws IOException { 544 rebootWipeUserData(context, false, context.getPackageName()); 545 } 546 547 /** {@hide} */ 548 public static void rebootWipeUserData(Context context, String reason) throws IOException { 549 rebootWipeUserData(context, false, reason); 550 } 551 552 /** {@hide} */ 553 public static void rebootWipeUserData(Context context, boolean shutdown) 554 throws IOException { 555 rebootWipeUserData(context, shutdown, context.getPackageName()); 556 } 557 558 /** 559 * Reboots the device and wipes the user data and cache 560 * partitions. This is sometimes called a "factory reset", which 561 * is something of a misnomer because the system partition is not 562 * restored to its factory state. Requires the 563 * {@link android.Manifest.permission#REBOOT} permission. 564 * 565 * @param context the Context to use 566 * @param shutdown if true, the device will be powered down after 567 * the wipe completes, rather than being rebooted 568 * back to the regular system. 569 * 570 * @throws IOException if writing the recovery command file 571 * fails, or if the reboot itself fails. 572 * @throws SecurityException if the current user is not allowed to wipe data. 573 * 574 * @hide 575 */ 576 public static void rebootWipeUserData(Context context, boolean shutdown, String reason) 577 throws IOException { 578 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 579 if (um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) { 580 throw new SecurityException("Wiping data is not allowed for this user."); 581 } 582 final ConditionVariable condition = new ConditionVariable(); 583 584 Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION"); 585 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 586 context.sendOrderedBroadcastAsUser(intent, UserHandle.SYSTEM, 587 android.Manifest.permission.MASTER_CLEAR, 588 new BroadcastReceiver() { 589 @Override 590 public void onReceive(Context context, Intent intent) { 591 condition.open(); 592 } 593 }, null, 0, null, null); 594 595 // Block until the ordered broadcast has completed. 596 condition.block(); 597 598 String shutdownArg = null; 599 if (shutdown) { 600 shutdownArg = "--shutdown_after"; 601 } 602 603 String reasonArg = null; 604 if (!TextUtils.isEmpty(reason)) { 605 reasonArg = "--reason=" + sanitizeArg(reason); 606 } 607 608 final String localeArg = "--locale=" + Locale.getDefault().toString(); 609 bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg); 610 } 611 612 /** 613 * Reboot into the recovery system to wipe the /cache partition. 614 * @throws IOException if something goes wrong. 615 */ 616 public static void rebootWipeCache(Context context) throws IOException { 617 rebootWipeCache(context, context.getPackageName()); 618 } 619 620 /** {@hide} */ 621 public static void rebootWipeCache(Context context, String reason) throws IOException { 622 String reasonArg = null; 623 if (!TextUtils.isEmpty(reason)) { 624 reasonArg = "--reason=" + sanitizeArg(reason); 625 } 626 627 final String localeArg = "--locale=" + Locale.getDefault().toString(); 628 bootCommand(context, "--wipe_cache", reasonArg, localeArg); 629 } 630 631 /** 632 * Reboot into the recovery system with the supplied argument. 633 * @param args to pass to the recovery utility. 634 * @throws IOException if something goes wrong. 635 */ 636 private static void bootCommand(Context context, String... args) throws IOException { 637 synchronized (sRequestLock) { 638 LOG_FILE.delete(); 639 640 StringBuilder command = new StringBuilder(); 641 for (String arg : args) { 642 if (!TextUtils.isEmpty(arg)) { 643 command.append(arg); 644 command.append("\n"); 645 } 646 } 647 648 // Write the command into BCB (bootloader control block). 649 RecoverySystem rs = (RecoverySystem) context.getSystemService( 650 Context.RECOVERY_SERVICE); 651 rs.setupBcb(command.toString()); 652 653 // Having set up the BCB, go ahead and reboot. 654 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 655 pm.reboot(PowerManager.REBOOT_RECOVERY); 656 657 throw new IOException("Reboot failed (no permissions?)"); 658 } 659 } 660 661 /** 662 * Called after booting to process and remove recovery-related files. 663 * @return the log file from recovery, or null if none was found. 664 * 665 * @hide 666 */ 667 public static String handleAftermath() { 668 // Record the tail of the LOG_FILE 669 String log = null; 670 try { 671 log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n"); 672 } catch (FileNotFoundException e) { 673 Log.i(TAG, "No recovery log file"); 674 } catch (IOException e) { 675 Log.e(TAG, "Error reading recovery log", e); 676 } 677 678 // Only remove the OTA package if it's partially processed (uncrypt'd). 679 boolean reservePackage = BLOCK_MAP_FILE.exists(); 680 if (!reservePackage && UNCRYPT_PACKAGE_FILE.exists()) { 681 String filename = null; 682 try { 683 filename = FileUtils.readTextFile(UNCRYPT_PACKAGE_FILE, 0, null); 684 } catch (IOException e) { 685 Log.e(TAG, "Error reading uncrypt file", e); 686 } 687 688 // Remove the OTA package on /data that has been (possibly 689 // partially) processed. (Bug: 24973532) 690 if (filename != null && filename.startsWith("/data")) { 691 if (UNCRYPT_PACKAGE_FILE.delete()) { 692 Log.i(TAG, "Deleted: " + filename); 693 } else { 694 Log.e(TAG, "Can't delete: " + filename); 695 } 696 } 697 } 698 699 // We keep the update logs (beginning with LAST_PREFIX), and optionally 700 // the block map file (BLOCK_MAP_FILE) for a package. BLOCK_MAP_FILE 701 // will be created at the end of a successful uncrypt. If seeing this 702 // file, we keep the block map file and the file that contains the 703 // package name (UNCRYPT_PACKAGE_FILE). This is to reduce the work for 704 // GmsCore to avoid re-downloading everything again. 705 String[] names = RECOVERY_DIR.list(); 706 for (int i = 0; names != null && i < names.length; i++) { 707 if (names[i].startsWith(LAST_PREFIX)) continue; 708 if (reservePackage && names[i].equals(BLOCK_MAP_FILE.getName())) continue; 709 if (reservePackage && names[i].equals(UNCRYPT_PACKAGE_FILE.getName())) continue; 710 711 recursiveDelete(new File(RECOVERY_DIR, names[i])); 712 } 713 714 return log; 715 } 716 717 /** 718 * Internally, delete a given file or directory recursively. 719 */ 720 private static void recursiveDelete(File name) { 721 if (name.isDirectory()) { 722 String[] files = name.list(); 723 for (int i = 0; files != null && i < files.length; i++) { 724 File f = new File(name, files[i]); 725 recursiveDelete(f); 726 } 727 } 728 729 if (!name.delete()) { 730 Log.e(TAG, "Can't delete: " + name); 731 } else { 732 Log.i(TAG, "Deleted: " + name); 733 } 734 } 735 736 /** 737 * Talks to RecoverySystemService via Binder to trigger uncrypt. 738 */ 739 private boolean uncrypt(String packageFile, IRecoverySystemProgressListener listener) { 740 try { 741 return mService.uncrypt(packageFile, listener); 742 } catch (RemoteException unused) { 743 } 744 return false; 745 } 746 747 /** 748 * Talks to RecoverySystemService via Binder to set up the BCB. 749 */ 750 private boolean setupBcb(String command) { 751 try { 752 return mService.setupBcb(command); 753 } catch (RemoteException unused) { 754 } 755 return false; 756 } 757 758 /** 759 * Talks to RecoverySystemService via Binder to clear up the BCB. 760 */ 761 private boolean clearBcb() { 762 try { 763 return mService.clearBcb(); 764 } catch (RemoteException unused) { 765 } 766 return false; 767 } 768 769 /** 770 * Internally, recovery treats each line of the command file as a separate 771 * argv, so we only need to protect against newlines and nulls. 772 */ 773 private static String sanitizeArg(String arg) { 774 arg = arg.replace('\0', '?'); 775 arg = arg.replace('\n', '?'); 776 return arg; 777 } 778 779 780 /** 781 * @removed Was previously made visible by accident. 782 */ 783 public RecoverySystem() { 784 mService = null; 785 } 786 787 /** 788 * @hide 789 */ 790 public RecoverySystem(IRecoverySystem service) { 791 mService = service; 792 } 793} 794