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