[Android] Enable multidex for release builds of chrome_public_test_apk. (RELAND)
BUG=712852
Review-Url: https://codereview.chromium.org/2839983002
Cr-Commit-Position: refs/heads/master@{#467462}
diff --git a/DEPS b/DEPS
index b811f3ff..be08e7a4 100644
--- a/DEPS
+++ b/DEPS
@@ -466,7 +466,7 @@
Var('chromium_git') + '/external/android_protobuf.git' + '@' + '999188d0dc72e97f7fe08bb756958a2cf090f4e7',
'src/third_party/android_tools':
- Var('chromium_git') + '/android_tools.git' + '@' + 'b65c4776dac2cf1b80e969b3b2d4e081b9c84f29',
+ Var('chromium_git') + '/android_tools.git' + '@' + 'cb6bc21107001e2f2eeee2707b482b2b755baf51',
'src/third_party/apache-portable-runtime/src':
Var('chromium_git') + '/external/apache-portable-runtime.git' + '@' + 'c76a8c4277e09a82eaa229e35246edea1ee0a6a1',
diff --git a/base/BUILD.gn b/base/BUILD.gn
index b284bec..43897be 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -2585,6 +2585,7 @@
"//testing/android/reporter:reporter_java",
"//third_party/android_support_test_runner:exposed_instrumentation_api_publish_java",
"//third_party/android_support_test_runner:runner_java",
+ "//third_party/android_tools:android_support_chromium_java",
"//third_party/hamcrest:hamcrest_core_java",
"//third_party/junit",
]
@@ -2594,6 +2595,7 @@
"test/android/javatests/src/org/chromium/base/test/BaseInstrumentationTestRunner.java",
"test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java",
"test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java",
+ "test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java",
"test/android/javatests/src/org/chromium/base/test/BaseTestResult.java",
"test/android/javatests/src/org/chromium/base/test/SetUpTestRule.java",
"test/android/javatests/src/org/chromium/base/test/SetUpStatement.java",
diff --git a/base/android/java/src/org/chromium/base/BaseChromiumApplication.java b/base/android/java/src/org/chromium/base/BaseChromiumApplication.java
index 6b97bdd..61109f1 100644
--- a/base/android/java/src/org/chromium/base/BaseChromiumApplication.java
+++ b/base/android/java/src/org/chromium/base/BaseChromiumApplication.java
@@ -44,7 +44,9 @@
super.attachBaseContext(base);
assert getBaseContext() != null;
checkAppBeingReplaced();
- ChromiumMultiDexInstaller.install(this);
+ if (BuildConfig.isMultidexEnabled()) {
+ ChromiumMultiDexInstaller.install(this);
+ }
}
/**
diff --git a/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java b/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java
index 3d036c2..9049cb0 100644
--- a/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java
+++ b/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java
@@ -12,7 +12,6 @@
import android.os.Build;
import android.support.multidex.MultiDex;
-import org.chromium.base.BuildConfig;
import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting;
@@ -48,8 +47,6 @@
*/
@VisibleForTesting
public static void install(Context context) {
- if (!BuildConfig.isMultidexEnabled()) return;
-
// TODO(jbudorick): Back out this version check once support for K & below works.
// http://crbug.com/512357
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
index fcd60869..a48c4ab 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
@@ -4,7 +4,8 @@
package org.chromium.base.test;
-import android.os.Bundle;
+import android.app.Application;
+import android.content.Context;
import android.support.test.runner.AndroidJUnitRunner;
import org.chromium.base.multidex.ChromiumMultiDexInstaller;
@@ -18,8 +19,11 @@
*/
public class BaseChromiumAndroidJUnitRunner extends AndroidJUnitRunner {
@Override
- public void onCreate(Bundle arguments) {
- ChromiumMultiDexInstaller.install(getTargetContext());
- super.onCreate(arguments);
+ public Application newApplication(ClassLoader cl, String className, Context context)
+ throws ClassNotFoundException, IllegalAccessException, InstantiationException {
+ ChromiumMultiDexInstaller.install(new BaseChromiumRunnerCommon.MultiDexContextWrapper(
+ getContext(), getTargetContext()));
+ BaseChromiumRunnerCommon.reorderDexPathElements(cl, getContext(), getTargetContext());
+ return super.newApplication(cl, className, context);
}
}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumInstrumentationTestRunner.java b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumInstrumentationTestRunner.java
index d44c9dd..693bb62 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumInstrumentationTestRunner.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumInstrumentationTestRunner.java
@@ -4,7 +4,8 @@
package org.chromium.base.test;
-import android.os.Bundle;
+import android.app.Application;
+import android.content.Context;
import org.chromium.base.multidex.ChromiumMultiDexInstaller;
import org.chromium.base.test.util.CommandLineFlags;
@@ -16,9 +17,12 @@
*/
public class BaseChromiumInstrumentationTestRunner extends BaseInstrumentationTestRunner {
@Override
- public void onCreate(Bundle arguments) {
- ChromiumMultiDexInstaller.install(getTargetContext());
- super.onCreate(arguments);
+ public Application newApplication(ClassLoader cl, String className, Context context)
+ throws ClassNotFoundException, IllegalAccessException, InstantiationException {
+ ChromiumMultiDexInstaller.install(new BaseChromiumRunnerCommon.MultiDexContextWrapper(
+ getContext(), getTargetContext()));
+ BaseChromiumRunnerCommon.reorderDexPathElements(cl, getContext(), getTargetContext());
+ return super.newApplication(cl, className, context);
}
/**
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java
new file mode 100644
index 0000000..fcda9103
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java
@@ -0,0 +1,162 @@
+// Copyright 2017 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.base.test;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+
+import org.chromium.android.support.PackageManagerWrapper;
+import org.chromium.base.Log;
+import org.chromium.base.annotations.MainDex;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Functionality common to the JUnit3 and JUnit4 runners.
+ */
+@MainDex
+class BaseChromiumRunnerCommon {
+ private static final String TAG = "base_test";
+
+ /**
+ * A ContextWrapper that allows multidex test APKs to extract secondary dexes into
+ * the APK under test's data directory.
+ */
+ @MainDex
+ static class MultiDexContextWrapper extends ContextWrapper {
+ private Context mAppContext;
+
+ MultiDexContextWrapper(Context instrContext, Context appContext) {
+ super(instrContext);
+ mAppContext = appContext;
+ }
+
+ @Override
+ public File getFilesDir() {
+ return mAppContext.getFilesDir();
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String name, int mode) {
+ return mAppContext.getSharedPreferences(name, mode);
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return new PackageManagerWrapper(super.getPackageManager()) {
+ @Override
+ public ApplicationInfo getApplicationInfo(String packageName, int flags) {
+ try {
+ ApplicationInfo ai = super.getApplicationInfo(packageName, flags);
+ if (packageName.equals(getPackageName())) {
+ ApplicationInfo appAi =
+ super.getApplicationInfo(mAppContext.getPackageName(), flags);
+ File dataDir = new File(appAi.dataDir, "test-multidex");
+ if (!dataDir.exists() && !dataDir.mkdirs()) {
+ throw new IOException(String.format(
+ "Unable to create test multidex directory \"%s\"",
+ dataDir.getPath()));
+ }
+ ai.dataDir = dataDir.getPath();
+ }
+ return ai;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get application info for %s", packageName, e);
+ }
+ return null;
+ }
+ };
+ }
+ }
+
+ /**
+ * Ensure all test dex entries precede app dex entries.
+ *
+ * @param cl ClassLoader to modify. Assumed to be a derivative of
+ * {@link dalvik.system.BaseDexClassLoader}. If this isn't
+ * the case, reordering will fail.
+ */
+ static void reorderDexPathElements(ClassLoader cl, Context context, Context targetContext) {
+ try {
+ Log.i(TAG,
+ "Reordering dex files. If you're building a multidex test APK and see a "
+ + "class resolving to an unexpected implementation, this may be why.");
+ Field pathListField = findField(cl, "pathList");
+ Object dexPathList = pathListField.get(cl);
+ Field dexElementsField = findField(dexPathList, "dexElements");
+ Object[] dexElementsList = (Object[]) dexElementsField.get(dexPathList);
+ Arrays.sort(dexElementsList,
+ new DexListReorderingComparator(
+ context.getPackageName(), targetContext.getPackageName()));
+ dexElementsField.set(dexPathList, dexElementsList);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to reorder dex elements for testing.", e);
+ }
+ }
+
+ /**
+ * Comparator for sorting dex list entries.
+ *
+ * Using this to sort a list of dex list entries will result in the following order:
+ * - Strings that contain neither the test package nor the app package in lexicographical
+ * order.
+ * - Strings that contain the test package in lexicographical order.
+ * - Strings that contain the app package but not the test package in lexicographical order.
+ */
+ private static class DexListReorderingComparator implements Comparator<Object>, Serializable {
+ private String mTestPackage;
+ private String mAppPackage;
+
+ public DexListReorderingComparator(String testPackage, String appPackage) {
+ mTestPackage = testPackage;
+ mAppPackage = appPackage;
+ }
+
+ @Override
+ public int compare(Object o1, Object o2) {
+ String s1 = o1.toString();
+ String s2 = o2.toString();
+ if (s1.contains(mTestPackage)) {
+ if (!s2.contains(mTestPackage)) {
+ if (s2.contains(mAppPackage)) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+ } else if (s1.contains(mAppPackage)) {
+ if (s2.contains(mTestPackage)) {
+ return 1;
+ } else if (!s2.contains(mAppPackage)) {
+ return 1;
+ }
+ } else if (s2.contains(mTestPackage) || s2.contains(mAppPackage)) {
+ return -1;
+ }
+ return s1.compareTo(s2);
+ }
+ }
+
+ private static Field findField(Object instance, String name) throws NoSuchFieldException {
+ for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
+ try {
+ Field f = clazz.getDeclaredField(name);
+ f.setAccessible(true);
+ return f;
+ } catch (NoSuchFieldException e) {
+ }
+ }
+ throw new NoSuchFieldException(
+ "Unable to find field " + name + " in " + instance.getClass());
+ }
+}
diff --git a/build/android/main_dex_classes.flags b/build/android/main_dex_classes.flags
index 81152dc..9bb7977a 100644
--- a/build/android/main_dex_classes.flags
+++ b/build/android/main_dex_classes.flags
@@ -1,3 +1,10 @@
+# Copyright 2017 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.
+
+# Proguard flags for what should be kept in the main dex. Only used
+# during main dex list determination, not during actual proguarding.
+
-keep @**.MainDex class * {
*;
}
diff --git a/build/android/multidex.flags b/build/android/multidex.flags
new file mode 100644
index 0000000..59e7e85
--- /dev/null
+++ b/build/android/multidex.flags
@@ -0,0 +1,12 @@
+# Copyright 2017 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.
+
+# Proguard flags for what to keep through proguarding when multidex is
+# enabled. Not used during main dex list determination.
+
+-keepattributes *Annotations*
+-keep @interface org.chromium.base.annotations.MainDex
+-keep @**.MainDex class * {
+ *;
+}
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 4455732..eddbbf3 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -1935,6 +1935,9 @@
if (defined(invoker.proguard_configs)) {
_proguard_configs += invoker.proguard_configs
}
+ if (enable_multidex) {
+ _proguard_configs += [ "//build/android/multidex.flags" ]
+ }
assert(_proguard_configs != []) # Mark as used.
_proguard_target = "${_template_name}__proguard"
proguard(_proguard_target) {
diff --git a/build/secondary/third_party/android_tools/BUILD.gn b/build/secondary/third_party/android_tools/BUILD.gn
index 853b040..3161db12 100644
--- a/build/secondary/third_party/android_tools/BUILD.gn
+++ b/build/secondary/third_party/android_tools/BUILD.gn
@@ -193,6 +193,11 @@
aar_path = "$lib_path/$_lib_name/$lib_version/$_lib_name-$lib_version.aar"
}
+android_library("android_support_chromium_java") {
+ testonly = true
+ java_files = [ "$android_sdk_root/extras/chromium/support/src/org/chromium/android/support/PackageManagerWrapper.java" ]
+}
+
# TODO(dgn): Remove this once no other target has a dependency on it
java_group("google_play_services_default_resources") {
deps = []
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 7f3c01c..d281862 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -884,6 +884,11 @@
"//net/android:net_test_support_apk",
]
proguard_enabled = !is_java_debug
+
+ # The test APK contains code from both the APK under test and the
+ # test APK when proguard is enabled. That causes this APK to exceed
+ # the dex limit.
+ enable_multidex = proguard_enabled
}
if (enable_vr) {