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