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