MultiDexExtractor.java revision f6d1f23926672c8dd61da515f8d1bcb37ef4292d
1667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu/*
2667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Copyright (C) 2013 The Android Open Source Project
3667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu *
4667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Licensed under the Apache License, Version 2.0 (the "License");
5667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * you may not use this file except in compliance with the License.
6667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * You may obtain a copy of the License at
7667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu *
8667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu *      http://www.apache.org/licenses/LICENSE-2.0
9667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu *
10667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Unless required by applicable law or agreed to in writing, software
11667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * distributed under the License is distributed on an "AS IS" BASIS,
12667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * See the License for the specific language governing permissions and
14667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * limitations under the License.
15667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */
16667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
17667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chupackage android.support.multidex;
18667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
191f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chuimport android.content.pm.ApplicationInfo;
201f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chuimport android.util.Log;
211f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chu
22667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.Closeable;
23667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.File;
24d9eda5550540306f2037e2db2aba2919fda90057Yohann Rousselimport java.io.FileFilter;
25f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chuimport java.io.FileInputStream;
26667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.FileNotFoundException;
27667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.FileOutputStream;
28667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.IOException;
29667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.InputStream;
30f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chuimport java.security.MessageDigest;
31f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chuimport java.security.NoSuchAlgorithmException;
32667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.ArrayList;
33667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.List;
34667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipEntry;
35f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chuimport java.util.zip.ZipException;
36667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipFile;
37667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipOutputStream;
38667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
39667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu/**
40667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Exposes application secondary dex files as files in the application data
41667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * directory.
42667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */
43667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chufinal class MultiDexExtractor {
44667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
45667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final String TAG = MultiDex.TAG;
46667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
47667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    /**
48667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * We look for additional dex files named {@code classes2.dex},
49667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * {@code classes3.dex}, etc.
50667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     */
51667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final String DEX_PREFIX = "classes";
52667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final String DEX_SUFFIX = ".dex";
53667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
54667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final String EXTRACTED_NAME_EXT = ".classes";
55667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final String EXTRACTED_SUFFIX = ".zip";
56f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    private static final int MAX_EXTRACT_ATTEMPTS = 3;
57f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    private static final int MAX_ATTEMPTS_NO_SUCH_ALGORITHM = 2;
58f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu
59f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu
60f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
61f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        'A', 'B', 'C', 'D', 'E', 'F' };
62667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
63667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final int BUFFER_SIZE = 0x4000;
64667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
65667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    /**
66667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * Extracts application secondary dexes into files in the application data
67667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * directory.
68667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     *
69667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * @return a list of files that were created. The list may be empty if there
70667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     *         are no secondary dex files.
71667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * @throws IOException if encounters a problem while reading or writing
72667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     *         secondary dex files
73667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     */
7452eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel    static List<File> load(ApplicationInfo applicationInfo, File dexDir)
75667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            throws IOException {
76667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
77d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel        File sourceApk = new File(applicationInfo.sourceDir);
78d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel        long lastModified = sourceApk.lastModified();
79d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel        String extractedFilePrefix = sourceApk.getName()
80667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                + EXTRACTED_NAME_EXT;
81667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
82d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel        prepareDexDir(dexDir, extractedFilePrefix, lastModified);
83667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
84667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        final List<File> files = new ArrayList<File>();
85667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        ZipFile apk = new ZipFile(applicationInfo.sourceDir);
86667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        try {
87667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
88667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            int secondaryNumber = 2;
89667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
90667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
91667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            while (dexFile != null) {
92667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
93667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                File extractedFile = new File(dexDir, fileName);
94667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                files.add(extractedFile);
95667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
96667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                if (!extractedFile.isFile()) {
97f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                    int numAttempts = 0;
98f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                    boolean isExtractionSuccessful = false;
99f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                    while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) {
100f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        numAttempts++;
101f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu
102f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        // Create a zip file (extractedFile) containing only the secondary dex file
103f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        // (dexFile) from the apk.
104f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        extract(apk, dexFile, extractedFile, extractedFilePrefix,
105f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                                lastModified);
106f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu
107f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        // Verify that the extracted file is indeed a zip file.
108f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        isExtractionSuccessful = verifyZipFile(extractedFile);
109f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu
110f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        // Log the sha1 of the extracted zip file
111f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "success" : "failed") +
112f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                                " - SHA1 of " + extractedFile.getAbsolutePath() + ": " +
113f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                                computeSha1Digest(extractedFile));
114f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        if (!isExtractionSuccessful) {
115f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                            // Delete the extracted file
116f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                            extractedFile.delete();
117f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        }
118f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                    }
119f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                    if (!isExtractionSuccessful) {
120f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        throw new IOException("Could not create zip file " +
121f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                                extractedFile.getAbsolutePath() + " for secondary dex (" +
122f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                                secondaryNumber + ")");
123f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                    }
124667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                }
125667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                secondaryNumber++;
126667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
127667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
128667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        } finally {
129667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            try {
130667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                apk.close();
131667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            } catch (IOException e) {
132667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                Log.w(TAG, "Failed to close resource", e);
133667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
134667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
135667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
136667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        return files;
137667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    }
138667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
139d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel    private static void prepareDexDir(File dexDir, final String extractedFilePrefix,
140d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel            final long sourceLastModified) throws IOException {
141667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        dexDir.mkdir();
142667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        if (!dexDir.isDirectory()) {
143667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            throw new IOException("Failed to create dex directory " + dexDir.getPath());
144667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
145667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
146667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        // Clean possible old files
147d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel        FileFilter filter = new FileFilter() {
148d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel
149667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            @Override
150d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel            public boolean accept(File pathname) {
151d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel                return (!pathname.getName().startsWith(extractedFilePrefix))
152d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel                    || (pathname.lastModified() < sourceLastModified);
153667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
154667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        };
155667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        File[] files = dexDir.listFiles(filter);
156667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        if (files == null) {
157667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ").");
158667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            return;
159667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
160667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        for (File oldFile : files) {
161667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            if (!oldFile.delete()) {
162667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                Log.w(TAG, "Failed to delete old file " + oldFile.getPath());
163667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
164667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
165667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    }
166667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
16752eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel    private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo,
168d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel            String extractedFilePrefix, long sourceLastModified)
169d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel                    throws IOException, FileNotFoundException {
170667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
171667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        InputStream in = apk.getInputStream(dexFile);
172667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        ZipOutputStream out = null;
173667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        File tmp = File.createTempFile(extractedFilePrefix, EXTRACTED_SUFFIX,
174667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                extractTo.getParentFile());
175667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        Log.i(TAG, "Extracting " + tmp.getPath());
176667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        try {
177667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            out = new ZipOutputStream(new FileOutputStream(tmp));
178667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            try {
179667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                ZipEntry classesDex = new ZipEntry("classes.dex");
180edf0717d4203bd7e9c9435019e3c256d564b4583Yohann Roussel                // keep zip entry time since it is the criteria used by Dalvik
181edf0717d4203bd7e9c9435019e3c256d564b4583Yohann Roussel                classesDex.setTime(dexFile.getTime());
182667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                out.putNextEntry(classesDex);
183667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
184667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                byte[] buffer = new byte[BUFFER_SIZE];
185667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                int length = in.read(buffer);
1861f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chu                while (length != -1) {
18752eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel                    out.write(buffer, 0, length);
188667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                    length = in.read(buffer);
189667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                }
190667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            } finally {
191667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                closeQuietly(out);
192667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
193d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel            if (!tmp.setLastModified(sourceLastModified)) {
194d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel                Log.e(TAG, "Failed to set time of \"" + tmp.getAbsolutePath() + "\"." +
195d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel                        " This may cause problems with later updates of the apk.");
196d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel            }
197667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            Log.i(TAG, "Renaming to " + extractTo.getPath());
198667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            if (!tmp.renameTo(extractTo)) {
199667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() + "\" to \"" +
200667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                        extractTo.getAbsolutePath() + "\"");
201667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
202667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        } finally {
203667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            closeQuietly(in);
204667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            tmp.delete(); // return status ignored
205667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
206667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    }
207667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
208667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    /**
209f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu     * Returns whether the file is a valid zip file.
210f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu     */
211f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    private static boolean verifyZipFile(File file) {
212f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        try {
213f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            ZipFile zipFile = new ZipFile(file);
214f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            try {
215f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                zipFile.close();
216f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            } catch (IOException e) {
217f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                Log.w(TAG, "Failed to close zip file: " + file.getAbsolutePath());
218f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            }
219f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            return true;
220f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        } catch (ZipException ex) {
221f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            Log.w(TAG, "File " + file.getAbsolutePath() + " is not a valid zip file.", ex);
222f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        } catch (IOException ex) {
223f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            Log.w(TAG, "Got an IOException trying to open zip file: " + file.getAbsolutePath(), ex);
224f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        }
225f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        return false;
226f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    }
227f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu
228f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    /**
229667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * Closes the given {@code Closeable}. Suppresses any IO exceptions.
230667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     */
231667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static void closeQuietly(Closeable closeable) {
232667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        try {
233667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            closeable.close();
234667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        } catch (IOException e) {
235667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            Log.w(TAG, "Failed to close resource", e);
236667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
237667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    }
238f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu
239f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    private static synchronized String computeSha1Digest(File file) {
240f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        MessageDigest messageDigest = getMessageDigest("SHA1");
241f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        if (messageDigest == null) {
242f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            return "";
243f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        }
244f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        FileInputStream in = null;
245f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        try {
246f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            in = new FileInputStream(file);
247f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            byte[] bytes = new byte[8192];
248f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            int byteCount;
249f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            while ((byteCount = in.read(bytes)) != -1) {
250f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                messageDigest.update(bytes, 0, byteCount);
251f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            }
252f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            return toHex(messageDigest.digest(), false /* zeroTerminated */)
253f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                    .toLowerCase();
254f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        } catch (IOException e) {
255f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            return "";
256f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        } finally {
257f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            if (in != null) {
258f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                closeQuietly(in);
259f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            }
260f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        }
261f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    }
262f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu
263f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    /**
264f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu     * Encodes a byte array as a hexadecimal representation of bytes.
265f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu     */
266f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    private static String toHex(byte[] in, boolean zeroTerminated) {
267f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        int length = in.length;
268f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        StringBuilder out = new StringBuilder(length * 2);
269f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        for (int i = 0; i < length; i++) {
270f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            if (zeroTerminated && i == length - 1 && (in[i] & 0xff) == 0) {
271f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                break;
272f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            }
273f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            out.append(HEX_DIGITS[(in[i] & 0xf0) >>> 4]);
274f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            out.append(HEX_DIGITS[in[i] & 0x0f]);
275f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        }
276f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        return out.toString();
277f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    }
278f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu
279f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    /**
280f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu     * Retrieves the message digest instance for a given hash algorithm. Makes
281f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu     * {@link #MAX_ATTEMPTS_NO_SUCH_ALGORITHM} to successfully retrieve the
282f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu     * MessageDigest or will return null.
283f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu     */
284f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    private static MessageDigest getMessageDigest(String hashAlgorithm) {
285f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        for (int i = 0; i < MAX_ATTEMPTS_NO_SUCH_ALGORITHM; i++) {
286f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            try {
287f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                MessageDigest messageDigest = MessageDigest.getInstance(hashAlgorithm);
288f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                if (messageDigest != null) {
289f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                    return messageDigest;
290f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                }
291f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            } catch (NoSuchAlgorithmException e) {
292f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                // try again - this is needed due to a bug in MessageDigest that can have corrupted
293f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                // internal state.
294f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                continue;
295f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            }
296f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        }
297f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        return null;
298f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    }
299667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu}
300