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