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