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