11af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker/*
21af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker * Copyright (C) 2010 The Android Open Source Project
31af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker *
41af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker * Licensed under the Apache License, Version 2.0 (the "License");
51af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker * you may not use this file except in compliance with the License.
61af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker * You may obtain a copy of the License at
71af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker *
81af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker *      http://www.apache.org/licenses/LICENSE-2.0
91af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker *
101af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker * Unless required by applicable law or agreed to in writing, software
111af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker * distributed under the License is distributed on an "AS IS" BASIS,
121af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker * See the License for the specific language governing permissions and
141af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker * limitations under the License.
151af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker */
161af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
171af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerpackage android.os;
181af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
194ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parksimport android.content.BroadcastReceiver;
204ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parksimport android.content.Context;
214ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parksimport android.content.Intent;
22fe0538098403b49ebd9219bf77236471bb5ca63bJulia Reynoldsimport android.os.UserManager;
23004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkeyimport android.text.TextUtils;
244ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parksimport android.util.Log;
254ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parks
261af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.io.ByteArrayInputStream;
271af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.io.File;
281af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.io.FileNotFoundException;
291af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.io.FileWriter;
301af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.io.IOException;
31e2d58e95a09590a63f1c597bb808b925bcab9a69Doug Zongkerimport java.io.InputStream;
321af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.io.RandomAccessFile;
331af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.security.GeneralSecurityException;
341af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.security.PublicKey;
351af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.security.Signature;
361af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.security.SignatureException;
371af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.security.cert.CertificateFactory;
381af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.security.cert.X509Certificate;
391af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.util.Enumeration;
401af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.util.HashSet;
411af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.util.Iterator;
421af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.util.List;
43e33b4007ee56e843d5e99cfb92627425a551058dDoug Zongkerimport java.util.Locale;
441af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.util.zip.ZipEntry;
451af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport java.util.zip.ZipFile;
461af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
471af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport org.apache.harmony.security.asn1.BerInputStream;
481af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport org.apache.harmony.security.pkcs7.ContentInfo;
491af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport org.apache.harmony.security.pkcs7.SignedData;
501af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerimport org.apache.harmony.security.pkcs7.SignerInfo;
5127e549428eb5ae77a0ae536f778b204430f8c743Kenny Rootimport org.apache.harmony.security.x509.Certificate;
521af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
531af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker/**
541af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker * RecoverySystem contains methods for interacting with the Android
551af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker * recovery system (the separate partition that can be used to install
561af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker * system updates, wipe user data, etc.)
571af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker */
581af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongkerpublic class RecoverySystem {
591af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    private static final String TAG = "RecoverySystem";
601af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
611af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    /**
621af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * Default location of zip file containing public keys (X509
631af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * certs) authorized to sign OTA updates.
641af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     */
651af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    private static final File DEFAULT_KEYSTORE =
661af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        new File("/system/etc/security/otacerts.zip");
671af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
681af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    /** Send progress to listeners no more often than this (in ms). */
691af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500;
701af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
711af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    /** Used to communicate with recovery.  See bootable/recovery/recovery.c. */
721af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    private static File RECOVERY_DIR = new File("/cache/recovery");
731af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
741af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    private static File LOG_FILE = new File(RECOVERY_DIR, "log");
753d5040f8d474713a1e148b0d64f16bb0435d6388Doug Zongker    private static String LAST_PREFIX = "last_";
761af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
771af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    // Length limits for reading files.
78c95142d4a0ab7bebb899167da17c70c3196abbe4Dan Egnor    private static int LOG_FILE_MAX_LENGTH = 64 * 1024;
791af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
801af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    /**
811af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * Interface definition for a callback to be invoked regularly as
821af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * verification proceeds.
831af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     */
841af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    public interface ProgressListener {
851af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        /**
861af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker         * Called periodically as the verification progresses.
871af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker         *
881af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker         * @param progress  the approximate percentage of the
891af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker         *        verification that has been completed, ranging from 0
901af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker         *        to 100 (inclusive).
911af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker         */
921af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        public void onProgress(int progress);
931af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    }
941af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
951af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    /** @return the set of certs that can be used to sign an OTA package. */
9627e549428eb5ae77a0ae536f778b204430f8c743Kenny Root    private static HashSet<X509Certificate> getTrustedCerts(File keystore)
971af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        throws IOException, GeneralSecurityException {
9827e549428eb5ae77a0ae536f778b204430f8c743Kenny Root        HashSet<X509Certificate> trusted = new HashSet<X509Certificate>();
991af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        if (keystore == null) {
1001af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            keystore = DEFAULT_KEYSTORE;
1011af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        }
1021af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        ZipFile zip = new ZipFile(keystore);
1031af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        try {
1041af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            CertificateFactory cf = CertificateFactory.getInstance("X.509");
1051af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            Enumeration<? extends ZipEntry> entries = zip.entries();
1061af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            while (entries.hasMoreElements()) {
1071af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                ZipEntry entry = entries.nextElement();
108e2d58e95a09590a63f1c597bb808b925bcab9a69Doug Zongker                InputStream is = zip.getInputStream(entry);
109e2d58e95a09590a63f1c597bb808b925bcab9a69Doug Zongker                try {
11027e549428eb5ae77a0ae536f778b204430f8c743Kenny Root                    trusted.add((X509Certificate) cf.generateCertificate(is));
111e2d58e95a09590a63f1c597bb808b925bcab9a69Doug Zongker                } finally {
112e2d58e95a09590a63f1c597bb808b925bcab9a69Doug Zongker                    is.close();
113e2d58e95a09590a63f1c597bb808b925bcab9a69Doug Zongker                }
1141af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
1151af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        } finally {
1161af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            zip.close();
1171af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        }
1181af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        return trusted;
1191af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    }
1201af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
1211af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    /**
1221af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * Verify the cryptographic signature of a system update package
1231af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * before installing it.  Note that the package is also verified
1241af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * separately by the installer once the device is rebooted into
1251af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * the recovery system.  This function will return only if the
1261af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * package was successfully verified; otherwise it will throw an
1271af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * exception.
1281af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     *
1291af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * Verification of a package can take significant time, so this
130cb95657326add53f81cd2f8a0ae0a1a0527ae799Doug Zongker     * function should not be called from a UI thread.  Interrupting
131cb95657326add53f81cd2f8a0ae0a1a0527ae799Doug Zongker     * the thread while this function is in progress will result in a
132cb95657326add53f81cd2f8a0ae0a1a0527ae799Doug Zongker     * SecurityException being thrown (and the thread's interrupt flag
133cb95657326add53f81cd2f8a0ae0a1a0527ae799Doug Zongker     * will be cleared).
1341af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     *
1351af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * @param packageFile  the package to be verified
1361af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * @param listener     an object to receive periodic progress
1371af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * updates as verification proceeds.  May be null.
1381af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * @param deviceCertsZipFile  the zip file of certificates whose
1391af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * public keys we will accept.  Verification succeeds if the
1401af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * package is signed by the private key corresponding to any
1411af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * public key in this file.  May be null to use the system default
1421af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * file (currently "/system/etc/security/otacerts.zip").
1431af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     *
1441af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * @throws IOException if there were any errors reading the
1451af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * package or certs files.
1461af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * @throws GeneralSecurityException if verification failed
1471af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     */
1481af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    public static void verifyPackage(File packageFile,
1491af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                                     ProgressListener listener,
1501af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                                     File deviceCertsZipFile)
1511af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        throws IOException, GeneralSecurityException {
1521af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        long fileLen = packageFile.length();
1531af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
1541af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        RandomAccessFile raf = new RandomAccessFile(packageFile, "r");
1551af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        try {
1561af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            int lastPercent = 0;
1571af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            long lastPublishTime = System.currentTimeMillis();
1581af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            if (listener != null) {
1591af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                listener.onProgress(lastPercent);
1601af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
1611af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
1621af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            raf.seek(fileLen - 6);
1631af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            byte[] footer = new byte[6];
1641af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            raf.readFully(footer);
1651af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
1661af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) {
1671af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                throw new SignatureException("no signature in file (no footer)");
1681af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
1691af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
1701af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
1711af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);
1721af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
1731af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            byte[] eocd = new byte[commentSize + 22];
1741af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            raf.seek(fileLen - (commentSize + 22));
1751af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            raf.readFully(eocd);
1761af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
1771af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            // Check that we have found the start of the
1781af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            // end-of-central-directory record.
1791af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b ||
1801af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) {
1811af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                throw new SignatureException("no signature in file (bad footer)");
1821af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
1831af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
1841af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            for (int i = 4; i < eocd.length-3; ++i) {
1851af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                if (eocd[i  ] == (byte)0x50 && eocd[i+1] == (byte)0x4b &&
1861af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                    eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) {
1871af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                    throw new SignatureException("EOCD marker found after start of EOCD");
1881af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                }
1891af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
1901af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
1911af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            // The following code is largely copied from
1921af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            // JarUtils.verifySignature().  We could just *call* that
1931af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            // method here if that function didn't read the entire
1941af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            // input (ie, the whole OTA package) into memory just to
1951af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            // compute its message digest.
1961af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
1971af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            BerInputStream bis = new BerInputStream(
1981af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));
1991af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis);
2001af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            SignedData signedData = info.getSignedData();
2011af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            if (signedData == null) {
2021af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                throw new IOException("signedData is null");
2031af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
20427e549428eb5ae77a0ae536f778b204430f8c743Kenny Root            List<Certificate> encCerts = signedData.getCertificates();
2051af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            if (encCerts.isEmpty()) {
2061af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                throw new IOException("encCerts is empty");
2071af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
2081af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            // Take the first certificate from the signature (packages
2091af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            // should contain only one).
21027e549428eb5ae77a0ae536f778b204430f8c743Kenny Root            Iterator<Certificate> it = encCerts.iterator();
2111af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            X509Certificate cert = null;
2121af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            if (it.hasNext()) {
21327e549428eb5ae77a0ae536f778b204430f8c743Kenny Root                CertificateFactory cf = CertificateFactory.getInstance("X.509");
21427e549428eb5ae77a0ae536f778b204430f8c743Kenny Root                InputStream is = new ByteArrayInputStream(it.next().getEncoded());
21527e549428eb5ae77a0ae536f778b204430f8c743Kenny Root                cert = (X509Certificate) cf.generateCertificate(is);
2161af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            } else {
2171af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                throw new SignatureException("signature contains no certificates");
2181af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
2191af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
22027e549428eb5ae77a0ae536f778b204430f8c743Kenny Root            List<SignerInfo> sigInfos = signedData.getSignerInfos();
2211af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            SignerInfo sigInfo;
2221af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            if (!sigInfos.isEmpty()) {
2231af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                sigInfo = (SignerInfo)sigInfos.get(0);
2241af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            } else {
2251af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                throw new IOException("no signer infos!");
2261af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
2271af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
2281af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            // Check that the public key of the certificate contained
2291af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            // in the package equals one of our trusted public keys.
2301af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
23127e549428eb5ae77a0ae536f778b204430f8c743Kenny Root            HashSet<X509Certificate> trusted = getTrustedCerts(
2321af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);
2331af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
2341af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            PublicKey signatureKey = cert.getPublicKey();
2351af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            boolean verified = false;
23627e549428eb5ae77a0ae536f778b204430f8c743Kenny Root            for (X509Certificate c : trusted) {
2371af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                if (c.getPublicKey().equals(signatureKey)) {
2381af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                    verified = true;
2391af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                    break;
2401af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                }
2411af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
2421af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            if (!verified) {
2431af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                throw new SignatureException("signature doesn't match any trusted key");
2441af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
2451af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
2461af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            // The signature cert matches a trusted key.  Now verify that
2471af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            // the digest in the cert matches the actual file data.
2481af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
249c9a9ffc5264c2c9405b7b98e1e993279e10f994fDoug Zongker            // The verifier in recovery only handles SHA1withRSA and
250c9a9ffc5264c2c9405b7b98e1e993279e10f994fDoug Zongker            // SHA256withRSA signatures.  SignApk chooses which to use
251c9a9ffc5264c2c9405b7b98e1e993279e10f994fDoug Zongker            // based on the signature algorithm of the cert:
252c9a9ffc5264c2c9405b7b98e1e993279e10f994fDoug Zongker            //
253c9a9ffc5264c2c9405b7b98e1e993279e10f994fDoug Zongker            //    "SHA256withRSA" cert -> "SHA256withRSA" signature
254c9a9ffc5264c2c9405b7b98e1e993279e10f994fDoug Zongker            //    "SHA1withRSA" cert   -> "SHA1withRSA" signature
255c9a9ffc5264c2c9405b7b98e1e993279e10f994fDoug Zongker            //    "MD5withRSA" cert    -> "SHA1withRSA" signature (for backwards compatibility)
256c9a9ffc5264c2c9405b7b98e1e993279e10f994fDoug Zongker            //    any other cert       -> SignApk fails
257c9a9ffc5264c2c9405b7b98e1e993279e10f994fDoug Zongker            //
258c9a9ffc5264c2c9405b7b98e1e993279e10f994fDoug Zongker            // Here we ignore whatever the cert says, and instead use
259c9a9ffc5264c2c9405b7b98e1e993279e10f994fDoug Zongker            // whatever algorithm is used by the signature.
2601af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
26166e40c36b3145f4d2f3ddd547bd66f27b12f6324Jesse Wilson            String da = sigInfo.getDigestAlgorithm();
2621af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            String dea = sigInfo.getDigestEncryptionAlgorithm();
2631af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            String alg = null;
2641af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            if (da == null || dea == null) {
2651af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                // fall back to the cert algorithm if the sig one
2661af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                // doesn't look right.
2671af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                alg = cert.getSigAlgName();
2681af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            } else {
2691af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                alg = da + "with" + dea;
2701af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
2711af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            Signature sig = Signature.getInstance(alg);
2721af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            sig.initVerify(cert);
2731af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
2741af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            // The signature covers all of the OTA package except the
2751af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            // archive comment and its 2-byte length.
2761af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            long toRead = fileLen - commentSize - 2;
2771af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            long soFar = 0;
2781af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            raf.seek(0);
2791af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            byte[] buffer = new byte[4096];
280cb95657326add53f81cd2f8a0ae0a1a0527ae799Doug Zongker            boolean interrupted = false;
2811af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            while (soFar < toRead) {
282cb95657326add53f81cd2f8a0ae0a1a0527ae799Doug Zongker                interrupted = Thread.interrupted();
283cb95657326add53f81cd2f8a0ae0a1a0527ae799Doug Zongker                if (interrupted) break;
2841af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                int size = buffer.length;
2851af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                if (soFar + size > toRead) {
2861af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                    size = (int)(toRead - soFar);
2871af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                }
2881af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                int read = raf.read(buffer, 0, size);
2891af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                sig.update(buffer, 0, read);
2901af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                soFar += read;
2911af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
2921af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                if (listener != null) {
2931af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                    long now = System.currentTimeMillis();
2941af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                    int p = (int)(soFar * 100 / toRead);
2951af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                    if (p > lastPercent &&
2961af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                        now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
2971af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                        lastPercent = p;
2981af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                        lastPublishTime = now;
2991af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                        listener.onProgress(lastPercent);
3001af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                    }
3011af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                }
3021af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
3031af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            if (listener != null) {
3041af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                listener.onProgress(100);
3051af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
3061af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
307cb95657326add53f81cd2f8a0ae0a1a0527ae799Doug Zongker            if (interrupted) {
308cb95657326add53f81cd2f8a0ae0a1a0527ae799Doug Zongker                throw new SignatureException("verification was interrupted");
309cb95657326add53f81cd2f8a0ae0a1a0527ae799Doug Zongker            }
310cb95657326add53f81cd2f8a0ae0a1a0527ae799Doug Zongker
3111af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            if (!sig.verify(sigInfo.getEncryptedDigest())) {
3121af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                throw new SignatureException("signature digest verification failed");
3131af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
3141af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        } finally {
3151af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            raf.close();
3161af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        }
3171af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    }
3181af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
3191af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    /**
3201af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * Reboots the device in order to install the given update
3211af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * package.
32264010e835057d4b85e2d90cf75cc562f3b5eb552Jeff Brown     * Requires the {@link android.Manifest.permission#REBOOT} permission.
3231af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     *
3241af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * @param context      the Context to use
3254baf641e7d96375eba3f9a3aff5400b9e3d28cd6Doug Zongker     * @param packageFile  the update package to install.  Must be on
3264baf641e7d96375eba3f9a3aff5400b9e3d28cd6Doug Zongker     * a partition mountable by recovery.  (The set of partitions
3274baf641e7d96375eba3f9a3aff5400b9e3d28cd6Doug Zongker     * known to recovery may vary from device to device.  Generally,
3284baf641e7d96375eba3f9a3aff5400b9e3d28cd6Doug Zongker     * /cache and /data are safe.)
3291af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     *
3301af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * @throws IOException  if writing the recovery command file
3311af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * fails, or if the reboot itself fails.
3321af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     */
3331af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    public static void installPackage(Context context, File packageFile)
3341af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        throws IOException {
3351af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        String filename = packageFile.getCanonicalPath();
3361af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
337004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey
338004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        final String filenameArg = "--update_package=" + filename;
339004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        final String localeArg = "--locale=" + Locale.getDefault().toString();
340004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        bootCommand(context, filenameArg, localeArg);
3411af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    }
3421af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
3431af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    /**
344cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     * Reboots the device and wipes the user data and cache
345cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     * partitions.  This is sometimes called a "factory reset", which
346cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     * is something of a misnomer because the system partition is not
347cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     * restored to its factory state.  Requires the
348cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     * {@link android.Manifest.permission#REBOOT} permission.
3491af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     *
3501af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * @param context  the Context to use
3511af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     *
3521af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * @throws IOException  if writing the recovery command file
3531af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * fails, or if the reboot itself fails.
354fe0538098403b49ebd9219bf77236471bb5ca63bJulia Reynolds     * @throws SecurityException if the current user is not allowed to wipe data.
3551af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     */
3564ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parks    public static void rebootWipeUserData(Context context) throws IOException {
357004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        rebootWipeUserData(context, false, context.getPackageName());
358004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey    }
359004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey
360004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey    /** {@hide} */
361004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey    public static void rebootWipeUserData(Context context, String reason) throws IOException {
362004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        rebootWipeUserData(context, false, reason);
363004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey    }
364004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey
365004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey    /** {@hide} */
366004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey    public static void rebootWipeUserData(Context context, boolean shutdown)
367004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey            throws IOException {
368004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        rebootWipeUserData(context, shutdown, context.getPackageName());
369cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker    }
370cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker
371cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker    /**
372cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     * Reboots the device and wipes the user data and cache
373cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     * partitions.  This is sometimes called a "factory reset", which
374cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     * is something of a misnomer because the system partition is not
375cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     * restored to its factory state.  Requires the
376cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     * {@link android.Manifest.permission#REBOOT} permission.
377cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     *
378cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     * @param context   the Context to use
379cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     * @param shutdown  if true, the device will be powered down after
380cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     *                  the wipe completes, rather than being rebooted
381cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     *                  back to the regular system.
382cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     *
383cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     * @throws IOException  if writing the recovery command file
384cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     * fails, or if the reboot itself fails.
385fe0538098403b49ebd9219bf77236471bb5ca63bJulia Reynolds     * @throws SecurityException if the current user is not allowed to wipe data.
386cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     *
387cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     * @hide
388cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker     */
389004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey    public static void rebootWipeUserData(Context context, boolean shutdown, String reason)
390004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey            throws IOException {
391fe0538098403b49ebd9219bf77236471bb5ca63bJulia Reynolds        UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
392fe0538098403b49ebd9219bf77236471bb5ca63bJulia Reynolds        if (um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) {
393fe0538098403b49ebd9219bf77236471bb5ca63bJulia Reynolds            throw new SecurityException("Wiping data is not allowed for this user.");
394fe0538098403b49ebd9219bf77236471bb5ca63bJulia Reynolds        }
3954ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parks        final ConditionVariable condition = new ConditionVariable();
3964ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parks
3974ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parks        Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION");
398e27ae55d6db8b5f80fb76c3e7637a834a14f5f0dChristopher Tate        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
3995ac72a29593ab9a20337a2225df52bdf4754be02Dianne Hackborn        context.sendOrderedBroadcastAsUser(intent, UserHandle.OWNER,
4005ac72a29593ab9a20337a2225df52bdf4754be02Dianne Hackborn                android.Manifest.permission.MASTER_CLEAR,
4014ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parks                new BroadcastReceiver() {
4024ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parks                    @Override
4034ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parks                    public void onReceive(Context context, Intent intent) {
4044ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parks                        condition.open();
4054ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parks                    }
4064ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parks                }, null, 0, null, null);
4074ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parks
4084ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parks        // Block until the ordered broadcast has completed.
4094ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parks        condition.block();
4104ca74dc4c2e0c68803e777cf47ed8e01b8e8444eJason parks
411004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        String shutdownArg = null;
412cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker        if (shutdown) {
413004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey            shutdownArg = "--shutdown_after";
414004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        }
415004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey
416004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        String reasonArg = null;
417004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        if (!TextUtils.isEmpty(reason)) {
418004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey            reasonArg = "--reason=" + sanitizeArg(reason);
419cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker        }
420cdf008883921c2eb7daf10c82687e9a36461eb16Doug Zongker
421004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        final String localeArg = "--locale=" + Locale.getDefault().toString();
422004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);
4231af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    }
4241af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
4251af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    /**
42633651201375d3670672964503994c410b8eeed7bDoug Zongker     * Reboot into the recovery system to wipe the /cache partition.
4271af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * @throws IOException if something goes wrong.
4281af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     */
42933651201375d3670672964503994c410b8eeed7bDoug Zongker    public static void rebootWipeCache(Context context) throws IOException {
430004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        rebootWipeCache(context, context.getPackageName());
431004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey    }
432004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey
433004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey    /** {@hide} */
434004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey    public static void rebootWipeCache(Context context, String reason) throws IOException {
435004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        String reasonArg = null;
436004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        if (!TextUtils.isEmpty(reason)) {
437004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey            reasonArg = "--reason=" + sanitizeArg(reason);
438004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        }
439004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey
440004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        final String localeArg = "--locale=" + Locale.getDefault().toString();
441004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        bootCommand(context, "--wipe_cache", reasonArg, localeArg);
4421af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    }
4431af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
4441af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    /**
4451af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * Reboot into the recovery system with the supplied argument.
446004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey     * @param args to pass to the recovery utility.
4471af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * @throws IOException if something goes wrong.
4481af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     */
449004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey    private static void bootCommand(Context context, String... args) throws IOException {
4501af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        RECOVERY_DIR.mkdirs();  // In case we need it
4511af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        COMMAND_FILE.delete();  // In case it's not writable
4521af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        LOG_FILE.delete();
4531af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
4541af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        FileWriter command = new FileWriter(COMMAND_FILE);
4551af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        try {
456004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey            for (String arg : args) {
457004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey                if (!TextUtils.isEmpty(arg)) {
458004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey                    command.write(arg);
459004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey                    command.write("\n");
460004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey                }
461004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey            }
4621af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        } finally {
4631af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            command.close();
4641af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        }
4651af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
4661af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        // Having written the command file, go ahead and reboot
4671af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
468183415e521d599ca5e33e5022fec5ec7dfe1c055Doug Zongker        pm.reboot(PowerManager.REBOOT_RECOVERY);
4691af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
4701af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        throw new IOException("Reboot failed (no permissions?)");
4711af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    }
4721af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
4731af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    /**
4741af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * Called after booting to process and remove recovery-related files.
4751af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * @return the log file from recovery, or null if none was found.
4761af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     *
4771af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     * @hide
4781af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker     */
4791af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    public static String handleAftermath() {
4801af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        // Record the tail of the LOG_FILE
4811af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        String log = null;
4821af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        try {
4831af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n");
4841af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        } catch (FileNotFoundException e) {
4851af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            Log.i(TAG, "No recovery log file");
4861af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        } catch (IOException e) {
4871af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            Log.e(TAG, "Error reading recovery log", e);
4881af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        }
4891af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
4903d5040f8d474713a1e148b0d64f16bb0435d6388Doug Zongker        // Delete everything in RECOVERY_DIR except those beginning
4913d5040f8d474713a1e148b0d64f16bb0435d6388Doug Zongker        // with LAST_PREFIX
4921af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        String[] names = RECOVERY_DIR.list();
4931af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        for (int i = 0; names != null && i < names.length; i++) {
4943d5040f8d474713a1e148b0d64f16bb0435d6388Doug Zongker            if (names[i].startsWith(LAST_PREFIX)) continue;
4951af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            File f = new File(RECOVERY_DIR, names[i]);
4961af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            if (!f.delete()) {
4971af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                Log.e(TAG, "Can't delete: " + f);
4981af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            } else {
4991af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker                Log.i(TAG, "Deleted: " + f);
5001af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker            }
5011af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        }
5021af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
5031af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker        return log;
5041af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    }
5051af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker
506004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey    /**
507004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey     * Internally, recovery treats each line of the command file as a separate
508004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey     * argv, so we only need to protect against newlines and nulls.
509004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey     */
510004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey    private static String sanitizeArg(String arg) {
511004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        arg = arg.replace('\0', '?');
512004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        arg = arg.replace('\n', '?');
513004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey        return arg;
514004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey    }
515004a4b20f8d3116e6a711525960d433fcfea4ee4Jeff Sharkey
5161af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker    private void RecoverySystem() { }  // Do not instantiate
5171af33d0ddc2f50ade146e4d48e2feb6f1d553427Doug Zongker}
518