Home | History | Annotate | Download | only in os
      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          */
    151         public void onProgress(int progress);
    152     }
    153 
    154     /** @return the set of certs that can be used to sign an OTA package. */
    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      */
    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
    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      */
    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")
    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)
    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)
    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)
    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)
    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)
    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)
    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      */
    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} */
    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} */
    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} */
    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      */
    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      */
    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} */
    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      */
    898     public static void rebootWipeCache(Context context) throws IOException {
    899         rebootWipeCache(context, context.getPackageName());
    900     }
    901 
    902     /** {@hide} */
    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     })
    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      */
    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      */
    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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   1101     public RecoverySystem() {
   1102         mService = null;
   1103     }
   1104 
   1105     /**
   1106      * @hide
   1107      */
   1108     public RecoverySystem(IRecoverySystem service) {
   1109         mService = service;
   1110     }
   1111 }
   1112