| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 package org.chromium.webapk.lib.client; | |
| 6 | |
| 7 import android.os.Build; | |
| 8 import android.util.Log; | |
| 9 | |
| 10 import dalvik.system.DexClassLoader; | |
| 11 import dalvik.system.DexFile; | |
| 12 | |
| 13 import java.io.File; | |
| 14 import java.io.IOException; | |
| 15 import java.lang.reflect.InvocationTargetException; | |
| 16 import java.lang.reflect.Method; | |
| 17 | |
| 18 /** | |
| 19 * This class provides a method to optimize .dex files. | |
| 20 * Note: This class is copied (mostly) verbatim from DexOptUtils in GMSCore. | |
| 21 */ | |
| 22 public class DexOptimizer { | |
| 23 private static final String TAG = "cr_DexOptimzer"; | |
| 24 | |
| 25 private static final String DEX_SUFFIX = ".dex"; | |
| 26 private static final String ODEX_SUFFIX = ".odex"; | |
| 27 | |
| 28 /** | |
| 29 * Creates optimized odex file for the specified dex file. | |
| 30 * @param dexFile Path to a dex file. | |
| 31 * @return True if the dex file was successfully optimized. | |
| 32 */ | |
| 33 public static boolean optimize(File dexFile) { | |
| 34 if (!dexFile.exists()) { | |
| 35 Log.e(TAG, "Dex file does not exist! " + dexFile.getAbsolutePath()); | |
| 36 return false; | |
| 37 } | |
| 38 | |
| 39 try { | |
| 40 if (!DexFile.isDexOptNeeded(dexFile.getAbsolutePath())) { | |
| 41 return true; | |
| 42 } | |
| 43 } catch (Exception e) { | |
| 44 Log.e(TAG, "Failed to check optimization status: " + e.toString() +
" : " | |
| 45 + e.getMessage()); | |
| 46 } | |
| 47 | |
| 48 File odexDir = null; | |
| 49 try { | |
| 50 odexDir = ensureOdexDirectory(dexFile); | |
| 51 } catch (IOException e) { | |
| 52 Log.e(TAG, "Failed to create odex directory! " + e.getMessage()); | |
| 53 return false; | |
| 54 } | |
| 55 | |
| 56 File generatedDexDir = odexDir; | |
| 57 if (generatedDexDir.equals(dexFile.getParentFile())) { | |
| 58 generatedDexDir = new File(odexDir, "optimized"); | |
| 59 generatedDexDir.mkdirs(); | |
| 60 } | |
| 61 | |
| 62 new DexClassLoader( | |
| 63 dexFile.getAbsolutePath(), | |
| 64 generatedDexDir.getAbsolutePath(), | |
| 65 null, | |
| 66 ClassLoader.getSystemClassLoader()); | |
| 67 File optimizedFile = new File(generatedDexDir, dexFile.getName()); | |
| 68 if (!optimizedFile.exists()) { | |
| 69 Log.e(TAG, "Failed to create dex."); | |
| 70 return false; | |
| 71 } | |
| 72 | |
| 73 File destOdexFile = new File(odexDir, replaceExtension(optimizedFile.get
Name(), "odex")); | |
| 74 if (!optimizedFile.renameTo(destOdexFile)) { | |
| 75 Log.e(TAG, "Failed to rename optimized file."); | |
| 76 return false; | |
| 77 } | |
| 78 | |
| 79 if (!destOdexFile.setReadable(true, false)) { | |
| 80 Log.e(TAG, "Failed to make odex world readable."); | |
| 81 return false; | |
| 82 } | |
| 83 | |
| 84 return true; | |
| 85 } | |
| 86 | |
| 87 /** | |
| 88 * Guesses the directory that DexClassLoader looks in for the odex file base
d on the | |
| 89 * Android OS version and the dex path. | |
| 90 * @param dexPath | |
| 91 * @return Guess for the default odex directory. | |
| 92 */ | |
| 93 private static File odexDirectory(File dexPath) { | |
| 94 int currentApiVersion = Build.VERSION.SDK_INT; | |
| 95 try { | |
| 96 if (currentApiVersion >= Build.VERSION_CODES.M) { | |
| 97 return new File( | |
| 98 dexPath.getParentFile(), "oat/" + VMRuntime.getCurrentIn
structionSet()); | |
| 99 } else if (currentApiVersion >= Build.VERSION_CODES.LOLLIPOP) { | |
| 100 return new File(dexPath.getParentFile(), VMRuntime.getCurrentIns
tructionSet()); | |
| 101 } else { | |
| 102 return dexPath.getParentFile(); | |
| 103 } | |
| 104 } catch (NoSuchMethodException e) { | |
| 105 return null; | |
| 106 } | |
| 107 } | |
| 108 | |
| 109 /** | |
| 110 * Guesses the directory that DexClassLoader looks in for the odex file base
d on the | |
| 111 * Android OS version and the dex path. Creates the directory if it does not
exist. | |
| 112 * @param dexPath | |
| 113 * @return Guess for the default odex directory. | |
| 114 */ | |
| 115 private static File ensureOdexDirectory(File dexPath) throws IOException { | |
| 116 File odexDir = odexDirectory(dexPath); | |
| 117 if (odexDir == null) { | |
| 118 throw new IOException("Failed to create odex cache directory. " | |
| 119 + "Could not determine odex directory."); | |
| 120 } | |
| 121 if (!odexDir.exists()) { | |
| 122 boolean success = odexDir.mkdirs(); | |
| 123 if (!success) { | |
| 124 throw new IOException( | |
| 125 "Failed to create odex cache directory in data directory
."); | |
| 126 } | |
| 127 // The full path to the odex must be traversable. | |
| 128 File root = dexPath.getParentFile(); | |
| 129 File dir = odexDir; | |
| 130 while (dir != null && !root.equals(dir)) { | |
| 131 if (!dir.setExecutable(true, false)) { | |
| 132 throw new IOException("Failed to make odex directory world t
raversable: " | |
| 133 + dir.getAbsolutePath()); | |
| 134 } | |
| 135 dir = dir.getParentFile(); | |
| 136 } | |
| 137 } | |
| 138 return odexDir; | |
| 139 } | |
| 140 | |
| 141 /** | |
| 142 * Replaces a file name's extension. | |
| 143 * | |
| 144 * @param name File name to modify. | |
| 145 * @param extension New extension. | |
| 146 * @return File name with new extension. | |
| 147 */ | |
| 148 private static String replaceExtension(String name, String extension) { | |
| 149 int lastDot = name.lastIndexOf("."); | |
| 150 StringBuilder sb = new StringBuilder(lastDot + extension.length()); | |
| 151 sb.append(name, 0, lastDot + 1); | |
| 152 sb.append(extension); | |
| 153 return sb.toString(); | |
| 154 } | |
| 155 | |
| 156 /** | |
| 157 * Makes use of a hidden API to retrieve the instruction set name for the cu
rrently | |
| 158 * executing process. This string is used to form the directory name for the
generated | |
| 159 * odex. | |
| 160 * | |
| 161 * - This API is not available on pre-L devices, but as the pre-L runtime di
d not scope odex | |
| 162 * files by <isa> on pre-L, this is not a problem. | |
| 163 * | |
| 164 * - For devices L+, it's still possible for this API to be missing. In that
case | |
| 165 * we will fallback to A) interpretation, and failing that B) generate an
odex in the | |
| 166 * client's file space. | |
| 167 */ | |
| 168 private static class VMRuntime { | |
| 169 @SuppressWarnings("unchecked") | |
| 170 public static String getCurrentInstructionSet() throws NoSuchMethodExcep
tion { | |
| 171 Method getCurrentInstructionSetMethod; | |
| 172 try { | |
| 173 Class c = Class.forName("dalvik.system.VMRuntime"); | |
| 174 getCurrentInstructionSetMethod = c.getDeclaredMethod("getCurrent
InstructionSet"); | |
| 175 } catch (ClassNotFoundException | NoSuchMethodException e) { | |
| 176 Log.w(TAG, "dalvik.system.VMRuntime#getCurrentInstructionSet is
unsupported.", e); | |
| 177 throw new NoSuchMethodException( | |
| 178 "dalvik.system.VMRuntime#getCurrentInstructionSet could
not be found."); | |
| 179 } | |
| 180 try { | |
| 181 return (String) getCurrentInstructionSetMethod.invoke(null); | |
| 182 } catch (IllegalAccessException | InvocationTargetException e) { | |
| 183 Log.w(TAG, "Failed to call dalvik.system.VMRuntime#getCurrentIns
tructionSet", e); | |
| 184 throw new NoSuchMethodException( | |
| 185 "dalvik.system.VMRuntime#getCurrentInstructionSet could
not be found."); | |
| 186 } | |
| 187 } | |
| 188 } | |
| 189 } | |
| OLD | NEW |