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