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