| Index: chrome/android/webapk/libs/client/src/org/chromium/webapk/lib/client/DexOptimizer.java
|
| diff --git a/chrome/android/webapk/libs/client/src/org/chromium/webapk/lib/client/DexOptimizer.java b/chrome/android/webapk/libs/client/src/org/chromium/webapk/lib/client/DexOptimizer.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..3dbfdbbe9ff46d40a0e6d8e5e394837a128164df
|
| --- /dev/null
|
| +++ b/chrome/android/webapk/libs/client/src/org/chromium/webapk/lib/client/DexOptimizer.java
|
| @@ -0,0 +1,194 @@
|
| +// Copyright 2016 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.webapk.lib.client;
|
| +
|
| +import android.os.Build;
|
| +import android.util.Log;
|
| +
|
| +import dalvik.system.DexClassLoader;
|
| +import dalvik.system.DexFile;
|
| +
|
| +import org.chromium.base.annotations.SuppressFBWarnings;
|
| +
|
| +import java.io.File;
|
| +import java.io.IOException;
|
| +import java.lang.reflect.InvocationTargetException;
|
| +import java.lang.reflect.Method;
|
| +
|
| +/**
|
| + * This class provides a method to optimize .dex files.
|
| + * Note: This class is copied (mostly) verbatim from DexOptUtils in GMSCore.
|
| + */
|
| +public class DexOptimizer {
|
| + private static final String TAG = "cr_DexOptimzer";
|
| +
|
| + private static final String DEX_SUFFIX = ".dex";
|
| + private static final String ODEX_SUFFIX = ".odex";
|
| +
|
| + /**
|
| + * Creates optimized odex file for the specified dex file.
|
| + * @param dexFile Path to a dex file.
|
| + * @return True if the dex file was successfully optimized.
|
| + */
|
| + @SuppressFBWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
|
| + public static boolean optimize(File dexFile) {
|
| + if (!dexFile.exists()) {
|
| + Log.e(TAG, "Dex file does not exist! " + dexFile.getAbsolutePath());
|
| + return false;
|
| + }
|
| +
|
| + try {
|
| + if (!DexFile.isDexOptNeeded(dexFile.getAbsolutePath())) {
|
| + return true;
|
| + }
|
| + } catch (Exception e) {
|
| + Log.e(TAG, "Failed to check optimization status: " + e.toString() + " : "
|
| + + e.getMessage());
|
| + }
|
| +
|
| + File odexDir = null;
|
| + try {
|
| + odexDir = ensureOdexDirectory(dexFile);
|
| + } catch (IOException e) {
|
| + Log.e(TAG, "Failed to create odex directory! " + e.getMessage());
|
| + return false;
|
| + }
|
| +
|
| + File generatedDexDir = odexDir;
|
| + if (generatedDexDir.equals(dexFile.getParentFile())) {
|
| + generatedDexDir = new File(odexDir, "optimized");
|
| + if (!generatedDexDir.exists() && !generatedDexDir.mkdirs()) {
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + new DexClassLoader(
|
| + dexFile.getAbsolutePath(),
|
| + generatedDexDir.getAbsolutePath(),
|
| + null,
|
| + ClassLoader.getSystemClassLoader());
|
| + File optimizedFile = new File(generatedDexDir, dexFile.getName());
|
| + if (!optimizedFile.exists()) {
|
| + Log.e(TAG, "Failed to create dex.");
|
| + return false;
|
| + }
|
| +
|
| + File destOdexFile = new File(odexDir, replaceExtension(optimizedFile.getName(), "odex"));
|
| + if (!optimizedFile.renameTo(destOdexFile)) {
|
| + Log.e(TAG, "Failed to rename optimized file.");
|
| + return false;
|
| + }
|
| +
|
| + if (!destOdexFile.setReadable(true, false)) {
|
| + Log.e(TAG, "Failed to make odex world readable.");
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * Guesses the directory that DexClassLoader looks in for the odex file based on the
|
| + * Android OS version and the dex path.
|
| + * @param dexPath
|
| + * @return Guess for the default odex directory.
|
| + */
|
| + private static File odexDirectory(File dexPath) {
|
| + int currentApiVersion = Build.VERSION.SDK_INT;
|
| + try {
|
| + if (currentApiVersion >= Build.VERSION_CODES.M) {
|
| + return new File(
|
| + dexPath.getParentFile(), "oat/" + VMRuntime.getCurrentInstructionSet());
|
| + } else if (currentApiVersion >= Build.VERSION_CODES.LOLLIPOP) {
|
| + return new File(dexPath.getParentFile(), VMRuntime.getCurrentInstructionSet());
|
| + } else {
|
| + return dexPath.getParentFile();
|
| + }
|
| + } catch (NoSuchMethodException e) {
|
| + return null;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Guesses the directory that DexClassLoader looks in for the odex file based on the
|
| + * Android OS version and the dex path. Creates the directory if it does not exist.
|
| + * @param dexPath
|
| + * @return Guess for the default odex directory.
|
| + */
|
| + private static File ensureOdexDirectory(File dexPath) throws IOException {
|
| + File odexDir = odexDirectory(dexPath);
|
| + if (odexDir == null) {
|
| + throw new IOException("Failed to create odex cache directory. "
|
| + + "Could not determine odex directory.");
|
| + }
|
| + if (!odexDir.exists()) {
|
| + boolean success = odexDir.mkdirs();
|
| + if (!success) {
|
| + throw new IOException(
|
| + "Failed to create odex cache directory in data directory.");
|
| + }
|
| + // The full path to the odex must be traversable.
|
| + File root = dexPath.getParentFile();
|
| + File dir = odexDir;
|
| + while (dir != null && !root.equals(dir)) {
|
| + if (!dir.setExecutable(true, false)) {
|
| + throw new IOException("Failed to make odex directory world traversable: "
|
| + + dir.getAbsolutePath());
|
| + }
|
| + dir = dir.getParentFile();
|
| + }
|
| + }
|
| + return odexDir;
|
| + }
|
| +
|
| + /**
|
| + * Replaces a file name's extension.
|
| + *
|
| + * @param name File name to modify.
|
| + * @param extension New extension.
|
| + * @return File name with new extension.
|
| + */
|
| + private static String replaceExtension(String name, String extension) {
|
| + int lastDot = name.lastIndexOf(".");
|
| + StringBuilder sb = new StringBuilder(lastDot + extension.length());
|
| + sb.append(name, 0, lastDot + 1);
|
| + sb.append(extension);
|
| + return sb.toString();
|
| + }
|
| +
|
| + /**
|
| + * Makes use of a hidden API to retrieve the instruction set name for the currently
|
| + * executing process. This string is used to form the directory name for the generated
|
| + * odex.
|
| + *
|
| + * - This API is not available on pre-L devices, but as the pre-L runtime did not scope odex
|
| + * files by <isa> on pre-L, this is not a problem.
|
| + *
|
| + * - For devices L+, it's still possible for this API to be missing. In that case
|
| + * we will fallback to A) interpretation, and failing that B) generate an odex in the
|
| + * client's file space.
|
| + */
|
| + private static class VMRuntime {
|
| + @SuppressWarnings("unchecked")
|
| + public static String getCurrentInstructionSet() throws NoSuchMethodException {
|
| + Method getCurrentInstructionSetMethod;
|
| + try {
|
| + Class c = Class.forName("dalvik.system.VMRuntime");
|
| + getCurrentInstructionSetMethod = c.getDeclaredMethod("getCurrentInstructionSet");
|
| + } catch (ClassNotFoundException | NoSuchMethodException e) {
|
| + Log.w(TAG, "dalvik.system.VMRuntime#getCurrentInstructionSet is unsupported.", e);
|
| + throw new NoSuchMethodException(
|
| + "dalvik.system.VMRuntime#getCurrentInstructionSet could not be found.");
|
| + }
|
| + try {
|
| + return (String) getCurrentInstructionSetMethod.invoke(null);
|
| + } catch (IllegalAccessException | InvocationTargetException e) {
|
| + Log.w(TAG, "Failed to call dalvik.system.VMRuntime#getCurrentInstructionSet", e);
|
| + throw new NoSuchMethodException(
|
| + "dalvik.system.VMRuntime#getCurrentInstructionSet could not be found.");
|
| + }
|
| + }
|
| + }
|
| +}
|
|
|