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