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