diff --git a/build-tools/scripts/RunTests.targets b/build-tools/scripts/RunTests.targets
index 626245cde70..d1a63274515 100644
--- a/build-tools/scripts/RunTests.targets
+++ b/build-tools/scripts/RunTests.targets
@@ -25,6 +25,7 @@
<_ApkTestProject Include="$(_TopDir)\tests\BCL-Tests\Xamarin.Android.Bcl-Tests\Xamarin.Android.Bcl-Tests.csproj" />
<_ApkTestProject Include="$(_TopDir)\tests\Xamarin.Forms-Performance-Integration\Droid\Xamarin.Forms.Performance.Integration.Droid.csproj" />
<_ApkTestProject Include="$(_TopDir)\tests\EmbeddedDSOs\EmbeddedDSO\EmbeddedDSO.csproj" />
+ <_ApkTestProject Include="$(_TopDir)\tests\Runtime-MultiDex\Mono.Android-TestsMultiDex.csproj" />
<_ApkTestProjectAot Include="$(_TopDir)\src\Mono.Android\Test\Mono.Android-Tests.csproj" />
<_ApkTestProjectBundle Include="$(_TopDir)\src\Mono.Android\Test\Mono.Android-Tests.csproj" />
diff --git a/build-tools/scripts/TestApks.targets b/build-tools/scripts/TestApks.targets
index 46c49db1b11..cf790a87d8d 100644
--- a/build-tools/scripts/TestApks.targets
+++ b/build-tools/scripts/TestApks.targets
@@ -128,6 +128,7 @@
diff --git a/src/Mono.Android/Test/Mono.Android-Test.Shared.projitems b/src/Mono.Android/Test/Mono.Android-Test.Shared.projitems
new file mode 100644
index 00000000000..168c8804db9
--- /dev/null
+++ b/src/Mono.Android/Test/Mono.Android-Test.Shared.projitems
@@ -0,0 +1,45 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ {0AB4956E-6FB9-4DA0-9D49-AB65A3FF403A}
+
+
+ Mono.Android-Test.Shared
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Mono.Android/Test/Mono.Android-Test.Shared.shproj b/src/Mono.Android/Test/Mono.Android-Test.Shared.shproj
new file mode 100644
index 00000000000..299b21eb003
--- /dev/null
+++ b/src/Mono.Android/Test/Mono.Android-Test.Shared.shproj
@@ -0,0 +1,11 @@
+
+
+
+ {0AB4956E-6FB9-4DA0-9D49-AB65A3FF403A}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Mono.Android/Test/Mono.Android-Tests.csproj b/src/Mono.Android/Test/Mono.Android-Tests.csproj
index fb6550333eb..001a4bf5194 100644
--- a/src/Mono.Android/Test/Mono.Android-Tests.csproj
+++ b/src/Mono.Android/Test/Mono.Android-Tests.csproj
@@ -21,6 +21,7 @@
v8.1
true
+
true
diff --git a/src/Xamarin.Android.Build.Tasks/Resources/MonkeyPatcher.java b/src/Xamarin.Android.Build.Tasks/Resources/MonkeyPatcher.java
new file mode 100644
index 00000000000..96935344eb8
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Resources/MonkeyPatcher.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Source https://android.googlesource.com/platform/tools/base/+/gradle_2.0.0/instant-run/instant-run-server/src/main/java/com/android/tools/fd/runtime/MonkeyPatcher.java
+ */
+package mono.android;
+import static android.os.Build.VERSION.SDK_INT;
+import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
+import static android.os.Build.VERSION_CODES.KITKAT;
+import static android.os.Build.VERSION_CODES.LOLLIPOP;
+import static android.os.Build.VERSION_CODES.M;
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.Build;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.view.ContextThemeWrapper;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+/**
+ * Code which handles live-patching resources in a running app
+ */
+public class MonkeyPatcher {
+ @SuppressWarnings("unchecked") // Lots of conversions with generic types
+ public static void monkeyPatchApplication(Context context,
+ Application bootstrap,
+ Application realApplication,
+ String externalResourceFile) {
+ /*
+ The code seems to perform this:
+ Application realApplication = the newly instantiated (in attachBaseContext) user app
+ currentActivityThread = ActivityThread.currentActivityThread;
+ Application initialApplication = currentActivityThread.mInitialApplication;
+ if (initialApplication == BootstrapApplication.this) {
+ currentActivityThread.mInitialApplication = realApplication;
+ // Replace all instance of the stub application in ActivityThread#mAllApplications with the
+ // real one
+ List allApplications = currentActivityThread.mAllApplications;
+ for (int i = 0; i < allApplications.size(); i++) {
+ if (allApplications.get(i) == BootstrapApplication.this) {
+ allApplications.set(i, realApplication);
+ }
+ }
+ // Enumerate all LoadedApk (or PackageInfo) fields in ActivityThread#mPackages and
+ // ActivityThread#mResourcePackages and do two things:
+ // - Replace the Application instance in its mApplication field with the real one
+ // - Replace mResDir to point to the external resource file instead of the .apk. This is
+ // used as the asset path for new Resources objects.
+ // - Set Application#mLoadedApk to the found LoadedApk instance
+ ArrayMap> map1 = currentActivityThread.mPackages;
+ for (Map.Entry> entry : map1.entrySet()) {
+ Object loadedApk = entry.getValue().get();
+ if (loadedApk == null) {
+ continue;
+ }
+ if (loadedApk.mApplication == BootstrapApplication.this) {
+ loadedApk.mApplication = realApplication;
+ if (externalResourceFile != null) {
+ loadedApk.mResDir = externalResourceFile;
+ }
+ realApplication.mLoadedApk = loadedApk;
+ }
+ }
+ // Exactly the same as above, except done for mResourcePackages instead of mPackages
+ ArrayMap> map2 = currentActivityThread.mResourcePackages;
+ for (Map.Entry> entry : map2.entrySet()) {
+ Object loadedApk = entry.getValue().get();
+ if (loadedApk == null) {
+ continue;
+ }
+ if (loadedApk.mApplication == BootstrapApplication.this) {
+ loadedApk.mApplication = realApplication;
+ if (externalResourceFile != null) {
+ loadedApk.mResDir = externalResourceFile;
+ }
+ realApplication.mLoadedApk = loadedApk;
+ }
+ }
+ */
+ // BootstrapApplication is created by reflection in Application#handleBindApplication() ->
+ // LoadedApk#makeApplication(), and its return value is used to set the Application field in all
+ // sorts of Android internals.
+ //
+ // Fortunately, Application#onCreate() is called quite soon after, so what we do is monkey
+ // patch in the real Application instance in BootstrapApplication#onCreate().
+ //
+ // A few places directly use the created Application instance (as opposed to the fields it is
+ // eventually stored in). Fortunately, it's easy to forward those to the actual real
+ // Application class.
+ try {
+ Log.v ("MonkeyPatcher" , "Patching Application with " + externalResourceFile);
+ // Find the ActivityThread instance for the current thread
+ Class> activityThread = Class.forName("android.app.ActivityThread");
+ Object currentActivityThread = getActivityThread(context, activityThread);
+ // Find the mInitialApplication field of the ActivityThread to the real application
+ Field mInitialApplication = activityThread.getDeclaredField("mInitialApplication");
+ mInitialApplication.setAccessible(true);
+ Application initialApplication = (Application) mInitialApplication.get(currentActivityThread);
+ if (realApplication != null && initialApplication == bootstrap) {
+ mInitialApplication.set(currentActivityThread, realApplication);
+ }
+ // Replace all instance of the stub application in ActivityThread#mAllApplications with the
+ // real one
+ if (realApplication != null) {
+ Field mAllApplications = activityThread.getDeclaredField("mAllApplications");
+ mAllApplications.setAccessible(true);
+ List allApplications = (List) mAllApplications
+ .get(currentActivityThread);
+ for (int i = 0; i < allApplications.size(); i++) {
+ if (allApplications.get(i) == bootstrap) {
+ allApplications.set(i, realApplication);
+ }
+ }
+ }
+
+ // Figure out how loaded APKs are stored.
+ // API version 8 has PackageInfo, 10 has LoadedApk. 9, I don't know.
+ Class> loadedApkClass;
+ try {
+ loadedApkClass = Class.forName("android.app.LoadedApk");
+ } catch (ClassNotFoundException e) {
+ loadedApkClass = Class.forName("android.app.ActivityThread$PackageInfo");
+ }
+ Field mApplication = loadedApkClass.getDeclaredField("mApplication");
+ mApplication.setAccessible(true);
+ Field mResDir = loadedApkClass.getDeclaredField("mResDir");
+ mResDir.setAccessible(true);
+ // 10 doesn't have this field, 14 does. Fortunately, there are not many Honeycomb devices
+ // floating around.
+ Field mLoadedApk = null;
+ try {
+ mLoadedApk = Application.class.getDeclaredField("mLoadedApk");
+ } catch (NoSuchFieldException e) {
+ // According to testing, it's okay to ignore this.
+ }
+ // Enumerate all LoadedApk (or PackageInfo) fields in ActivityThread#mPackages and
+ // ActivityThread#mResourcePackages and do two things:
+ // - Replace the Application instance in its mApplication field with the real one
+ // - Replace mResDir to point to the external resource file instead of the .apk. This is
+ // used as the asset path for new Resources objects.
+ // - Set Application#mLoadedApk to the found LoadedApk instance
+ for (String fieldName : new String[]{"mPackages", "mResourcePackages"}) {
+ Field field = activityThread.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ Object value = field.get(currentActivityThread);
+ for (Map.Entry> entry :
+ ((Map>) value).entrySet()) {
+ Object loadedApk = entry.getValue().get();
+ if (loadedApk == null) {
+ Log.v ("MonkeyPatcher" , "no loadedApK :(");
+ continue;
+ }
+ if (externalResourceFile != null) {
+ Log.v ("MonkeyPatcher" , "Patched mResDir");
+ mResDir.set(loadedApk, externalResourceFile);
+ }
+ }
+ }
+ } catch (Throwable e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ /**
+ * This utility method has nothing to do with the MonkeyPatcher per se.
+ * It simply calls the {@code currentActivityThread} method of {@code ActivityThread}.
+ */
+ public static Object getActivityThread(Context context,
+ Class> activityThread) {
+ try {
+ if (activityThread == null) {
+ activityThread = Class.forName("android.app.ActivityThread");
+ }
+ Method m = activityThread.getMethod("currentActivityThread");
+ m.setAccessible(true);
+ Object currentActivityThread = m.invoke(null);
+ if (currentActivityThread == null && context != null) {
+ // In older versions of Android (prior to frameworks/base 66a017b63461a22842)
+ // the currentActivityThread was built on thread locals, so we'll need to try
+ // even harder
+ Field mLoadedApk = context.getClass().getField("mLoadedApk");
+ mLoadedApk.setAccessible(true);
+ Object apk = mLoadedApk.get(context);
+ Field mActivityThreadField = apk.getClass().getDeclaredField("mActivityThread");
+ mActivityThreadField.setAccessible(true);
+ currentActivityThread = mActivityThreadField.get(apk);
+ }
+ return currentActivityThread;
+ } catch (Throwable ignore) {
+ return null;
+ }
+ }
+ public static void monkeyPatchExistingResources(Context context,
+ String externalResourceFile,
+ Collection activities) {
+ if (externalResourceFile == null) {
+ return;
+ }
+ try {
+ // Create a new AssetManager instance and point it to the resources installed under
+ // /sdcard
+ AssetManager newAssetManager = AssetManager.class.getConstructor().newInstance();
+ Method mAddAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
+ mAddAssetPath.setAccessible(true);
+ if (((Integer) mAddAssetPath.invoke(newAssetManager, externalResourceFile)) == 0) {
+ throw new IllegalStateException("Could not create new AssetManager");
+ }
+ // Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm
+ // in L, so we do it unconditionally.
+ Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks");
+ mEnsureStringBlocks.setAccessible(true);
+ mEnsureStringBlocks.invoke(newAssetManager);
+ if (activities != null) {
+ for (Activity activity : activities) {
+ Resources resources = activity.getResources();
+ try {
+ Field mAssets = Resources.class.getDeclaredField("mAssets");
+ mAssets.setAccessible(true);
+ mAssets.set(resources, newAssetManager);
+ } catch (Throwable ignore) {
+ Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
+ mResourcesImpl.setAccessible(true);
+ Object resourceImpl = mResourcesImpl.get(resources);
+ Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");
+ implAssets.setAccessible(true);
+ implAssets.set(resourceImpl, newAssetManager);
+ }
+ Resources.Theme theme = activity.getTheme();
+ try {
+ try {
+ Field ma = Resources.Theme.class.getDeclaredField("mAssets");
+ ma.setAccessible(true);
+ ma.set(theme, newAssetManager);
+ } catch (NoSuchFieldException ignore) {
+ Field themeField = Resources.Theme.class.getDeclaredField("mThemeImpl");
+ themeField.setAccessible(true);
+ Object impl = themeField.get(theme);
+ Field ma = impl.getClass().getDeclaredField("mAssets");
+ ma.setAccessible(true);
+ ma.set(impl, newAssetManager);
+ }
+ Field mt = ContextThemeWrapper.class.getDeclaredField("mTheme");
+ mt.setAccessible(true);
+ mt.set(activity, null);
+ Method mtm = ContextThemeWrapper.class.getDeclaredMethod("initializeTheme");
+ mtm.setAccessible(true);
+ mtm.invoke(activity);
+ if (SDK_INT < 24) { // As of API 24, mTheme is gone (but updates work
+ // without these changes
+ Method mCreateTheme = AssetManager.class
+ .getDeclaredMethod("createTheme");
+ mCreateTheme.setAccessible(true);
+ Object internalTheme = mCreateTheme.invoke(newAssetManager);
+ Field mTheme = Resources.Theme.class.getDeclaredField("mTheme");
+ mTheme.setAccessible(true);
+ mTheme.set(theme, internalTheme);
+ }
+ } catch (Throwable e) {
+ Log.e("MonkeyPatcher", "Failed to update existing theme for activity " + activity,
+ e);
+ }
+ pruneResourceCaches(resources);
+ }
+ }
+ // Iterate over all known Resources objects
+ Collection> references;
+ if (SDK_INT >= KITKAT) {
+ // Find the singleton instance of ResourcesManager
+ Class> resourcesManagerClass = Class.forName("android.app.ResourcesManager");
+ Method mGetInstance = resourcesManagerClass.getDeclaredMethod("getInstance");
+ mGetInstance.setAccessible(true);
+ Object resourcesManager = mGetInstance.invoke(null);
+ try {
+ Field fMActiveResources = resourcesManagerClass.getDeclaredField("mActiveResources");
+ fMActiveResources.setAccessible(true);
+ @SuppressWarnings("unchecked")
+ ArrayMap, WeakReference> arrayMap =
+ (ArrayMap, WeakReference>) fMActiveResources.get(resourcesManager);
+ references = arrayMap.values();
+ } catch (NoSuchFieldException ignore) {
+ Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences");
+ mResourceReferences.setAccessible(true);
+ //noinspection unchecked
+ references = (Collection>) mResourceReferences.get(resourcesManager);
+ }
+ } else {
+ Class> activityThread = Class.forName("android.app.ActivityThread");
+ Field fMActiveResources = activityThread.getDeclaredField("mActiveResources");
+ fMActiveResources.setAccessible(true);
+ Object thread = getActivityThread(context, activityThread);
+ @SuppressWarnings("unchecked")
+ HashMap, WeakReference> map =
+ (HashMap, WeakReference>) fMActiveResources.get(thread);
+ references = map.values();
+ }
+ for (WeakReference wr : references) {
+ Resources resources = wr.get();
+ if (resources != null) {
+ // Set the AssetManager of the Resources instance to our brand new one
+ try {
+ Field mAssets = Resources.class.getDeclaredField("mAssets");
+ mAssets.setAccessible(true);
+ mAssets.set(resources, newAssetManager);
+ } catch (Throwable ignore) {
+ Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
+ mResourcesImpl.setAccessible(true);
+ Object resourceImpl = mResourcesImpl.get(resources);
+ Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");
+ implAssets.setAccessible(true);
+ implAssets.set(resourceImpl, newAssetManager);
+ }
+ resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics());
+ }
+ }
+ } catch (Throwable e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ private static void pruneResourceCaches(Object resources) {
+ // Drain TypedArray instances from the typed array pool since these can hold on
+ // to stale asset data
+ if (SDK_INT >= LOLLIPOP) {
+ try {
+ Field typedArrayPoolField =
+ Resources.class.getDeclaredField("mTypedArrayPool");
+ typedArrayPoolField.setAccessible(true);
+ Object pool = typedArrayPoolField.get(resources);
+ Class> poolClass = pool.getClass();
+ Method acquireMethod = poolClass.getDeclaredMethod("acquire");
+ acquireMethod.setAccessible(true);
+ while (true) {
+ Object typedArray = acquireMethod.invoke(pool);
+ if (typedArray == null) {
+ break;
+ }
+ }
+ } catch (Throwable ignore) {
+ }
+ }
+ if (SDK_INT >= Build.VERSION_CODES.M) {
+ // Really should only be N; fix this as soon as it has its own API level
+ try {
+ Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
+ mResourcesImpl.setAccessible(true);
+ // For the remainder, use the ResourcesImpl instead, where all the fields
+ // now live
+ resources = mResourcesImpl.get(resources);
+ } catch (Throwable ignore) {
+ }
+ }
+ // Prune bitmap and color state lists etc caches
+ Object lock = null;
+ if (SDK_INT >= JELLY_BEAN_MR2) {
+ try {
+ Field field = resources.getClass().getDeclaredField("mAccessLock");
+ field.setAccessible(true);
+ lock = field.get(resources);
+ } catch (Throwable ignore) {
+ }
+ } else {
+ try {
+ Field field = Resources.class.getDeclaredField("mTmpValue");
+ field.setAccessible(true);
+ lock = field.get(resources);
+ } catch (Throwable ignore) {
+ }
+ }
+ if (lock == null) {
+ lock = MonkeyPatcher.class;
+ }
+ //noinspection SynchronizationOnLocalVariableOrMethodParameter
+ synchronized (lock) {
+ // Prune bitmap and color caches
+ pruneResourceCache(resources, "mDrawableCache");
+ pruneResourceCache(resources,"mColorDrawableCache");
+ pruneResourceCache(resources,"mColorStateListCache");
+ if (SDK_INT >= M) {
+ pruneResourceCache(resources, "mAnimatorCache");
+ pruneResourceCache(resources, "mStateListAnimatorCache");
+ } else if (SDK_INT == KITKAT) {
+ pruneResourceCache(resources, "sPreloadedDrawables");
+ pruneResourceCache(resources, "sPreloadedColorDrawables");
+ pruneResourceCache(resources, "sPreloadedColorStateLists");
+ }
+ }
+ }
+ private static boolean pruneResourceCache(Object resources,
+ String fieldName) {
+ try {
+ Class> resourcesClass = resources.getClass();
+ Field cacheField;
+ try {
+ cacheField = resourcesClass.getDeclaredField(fieldName);
+ } catch (NoSuchFieldException ignore) {
+ cacheField = Resources.class.getDeclaredField(fieldName);
+ }
+ cacheField.setAccessible(true);
+ Object cache = cacheField.get(resources);
+ // Find the class which defines the onConfigurationChange method
+ Class> type = cacheField.getType();
+ if (SDK_INT < JELLY_BEAN) {
+ if (cache instanceof SparseArray) {
+ ((SparseArray) cache).clear();
+ return true;
+ } else if (SDK_INT >= ICE_CREAM_SANDWICH && cache instanceof LongSparseArray) {
+ // LongSparseArray has API level 16 but was private (and available inside
+ // the framework) in 15 and is used for this cache.
+ //noinspection AndroidLintNewApi
+ ((LongSparseArray) cache).clear();
+ return true;
+ }
+ } else if (SDK_INT < M) {
+ // JellyBean, KitKat, Lollipop
+ if ("mColorStateListCache".equals(fieldName)) {
+ // For some reason framework doesn't call clearDrawableCachesLocked on
+ // this field
+ if (cache instanceof LongSparseArray) {
+ //noinspection AndroidLintNewApi
+ ((LongSparseArray)cache).clear();
+ }
+ } else if (type.isAssignableFrom(ArrayMap.class)) {
+ Method clearArrayMap = Resources.class.getDeclaredMethod(
+ "clearDrawableCachesLocked", ArrayMap.class, Integer.TYPE);
+ clearArrayMap.setAccessible(true);
+ clearArrayMap.invoke(resources, cache, -1);
+ return true;
+ } else if (type.isAssignableFrom(LongSparseArray.class)) {
+ try {
+ Method clearSparseMap = Resources.class.getDeclaredMethod(
+ "clearDrawableCachesLocked", LongSparseArray.class, Integer.TYPE);
+ clearSparseMap.setAccessible(true);
+ clearSparseMap.invoke(resources, cache, -1);
+ return true;
+ } catch (NoSuchMethodException e) {
+ if (cache instanceof LongSparseArray) {
+ //noinspection AndroidLintNewApi
+ ((LongSparseArray)cache).clear();
+ return true;
+ }
+ }
+ } else if (type.isArray() &&
+ type.getComponentType().isAssignableFrom(LongSparseArray.class)) {
+ LongSparseArray[] arrays = (LongSparseArray[])cache;
+ for (LongSparseArray array : arrays) {
+ if (array != null) {
+ //noinspection AndroidLintNewApi
+ array.clear();
+ }
+ }
+ return true;
+ }
+ } else {
+ // Marshmallow: DrawableCache class
+ while (type != null) {
+ try {
+ Method configChangeMethod = type.getDeclaredMethod(
+ "onConfigurationChange", Integer.TYPE);
+ configChangeMethod.setAccessible(true);
+ configChangeMethod.invoke(cache, -1);
+ return true;
+ } catch (Throwable ignore) {
+ }
+ type = type.getSuperclass();
+ }
+ }
+ } catch (Throwable ignore) {
+ // Not logging these; while there is some checking of SDK_INT here to avoid
+ // doing a lot of unnecessary field lookups, it's not entirely accurate and
+ // errs on the side of caution (since different devices may have picked up
+ // different snapshots of the framework); therefore, it's normal for this
+ // to attempt to look up a field for a cache that isn't there; only if it's
+ // really there will it continue to flush that particular cache.
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/Xamarin.Android.Build.Tasks/Resources/MonoPackageManager.java b/src/Xamarin.Android.Build.Tasks/Resources/MonoPackageManager.java
index d43792d46de..1d956fa4e84 100644
--- a/src/Xamarin.Android.Build.Tasks/Resources/MonoPackageManager.java
+++ b/src/Xamarin.Android.Build.Tasks/Resources/MonoPackageManager.java
@@ -5,6 +5,7 @@
import java.util.Locale;
import java.util.HashSet;
import java.util.zip.*;
+import java.util.Arrays;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -21,6 +22,7 @@ public class MonoPackageManager {
public static void LoadApplication (Context context, ApplicationInfo runtimePackage, String[] apks)
{
+ Log.v("MonoPackageManager", "LoadApplication Called");
synchronized (lock) {
if (context instanceof android.app.Application) {
Context = context;
@@ -30,8 +32,7 @@ public static void LoadApplication (Context context, ApplicationInfo runtimePack
android.content.Intent.ACTION_TIMEZONE_CHANGED
);
context.registerReceiver (new mono.android.app.NotifyTimeZoneChanges (), timezoneChangedFilter);
-
- System.loadLibrary("monodroid");
+
Locale locale = Locale.getDefault ();
String language = locale.getLanguage () + "-" + locale.getCountry ();
String filesDir = context.getFilesDir ().getAbsolutePath ();
@@ -46,6 +47,16 @@ public static void LoadApplication (Context context, ApplicationInfo runtimePack
external0,
"../legacy/Android/data/" + context.getPackageName () + "/files/.__override__").getAbsolutePath ();
+ Log.v("MonoPackageManager", "Loading monodroid " + System.getProperty("java.library.path") + " " + dataDir);
+ System.loadLibrary("monodroid");
+
+ Log.v("MonoPackageManager", "Language = " + language);
+ Log.v("MonoPackageManager", "apks = " + Arrays.toString(apks));
+ Log.v("MonoPackageManager", "nativeLibraryPath = " + getNativeLibraryPath (runtimePackage));
+ Log.v("MonoPackageManager", "dirs = " + filesDir + ":" + cacheDir + ":" + dataDir);
+ Log.v("MonoPackageManager", "external = " + externalDir + ":" + externalLegacyDir);
+ Log.v("MonoPackageManager", "assemblies = " + Arrays.toString(MonoPackageManager_Resources.Assemblies));
+ Log.v("MonoPackageManager", "packagename = " + context.getPackageName ());
Runtime.init (
language,
apks,
diff --git a/src/Xamarin.Android.Build.Tasks/Resources/MultiDexLoader.Debug.java b/src/Xamarin.Android.Build.Tasks/Resources/MultiDexLoader.Debug.java
new file mode 100644
index 00000000000..75826998ed3
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Resources/MultiDexLoader.Debug.java
@@ -0,0 +1,233 @@
+//
+// This is a modified version of Bazel source code. Its copyright lines follow below.
+//
+
+// Copyright 2015 Xamarin Inc. All rights reserved.
+// Copyright 2017 Microsoft Corporation. All rights reserved.
+
+//
+// Copyright 2014 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package mono.android;
+
+import mono.android.incrementaldeployment.IncrementalClassLoader;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Collection;
+import android.app.Application;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import android.app.Activity;
+import android.content.*;
+import android.util.Log;
+import android.util.ArrayMap;
+import android.os.Build;
+import dalvik.system.BaseDexClassLoader;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.view.ContextThemeWrapper;
+
+public class MultiDexLoader extends ContentProvider {
+
+ @Override
+ public boolean onCreate ()
+ {
+ return true;
+ }
+
+ @Override
+ public void attachInfo (android.content.Context context, android.content.pm.ProviderInfo info)
+ {
+ mIncrementalDeploymentDir = getIncrementalDeploymentDir (context);
+ String externalResourceFile = getExternalResourceFile ();
+
+ File codeCacheDir = context.getCacheDir ();
+ String nativeLibDir = context.getApplicationInfo ().nativeLibraryDir;
+ String dataDir = context.getApplicationInfo ().dataDir;
+ String packageName = context.getPackageName ();
+
+ List dexes = getDexList (packageName);
+ if (dexes != null && dexes.size () > 0) {
+ IncrementalClassLoader.inject (
+ MultiDexLoader.class.getClassLoader (),
+ packageName,
+ codeCacheDir,
+ nativeLibDir,
+ dexes);
+ }
+ super.attachInfo (context, info);
+ MonkeyPatcher.monkeyPatchApplication (context, null, null, externalResourceFile);
+ MonkeyPatcher.monkeyPatchExistingResources (context, externalResourceFile, getActivities (context, false));
+ }
+
+ private String mIncrementalDeploymentDir;
+
+ private static String getIncrementalDeploymentDir (Context context)
+ {
+ // For initial setup by Seppuku, it needs to create the dex deployment directory at app bootstrap.
+ // dex is special, important for mono runtime bootstrap.
+ String dir = new File (
+ android.os.Environment.getExternalStorageDirectory (),
+ "Android/data/" + context.getPackageName ()).getAbsolutePath ();
+ dir = new File (dir).exists () ?
+ dir + "/files" :
+ "/data/data/" + context.getPackageName () + "/files";
+ String dexDir = dir + "/.__override__/dexes";
+ if (!new File (dexDir).exists ())
+ new File (dexDir).mkdirs ();
+ return dir + "/";
+ }
+
+ private String getExternalResourceFile () {
+ String base = mIncrementalDeploymentDir;
+ String resourceFile = base + ".__override__/packaged/packaged_resources";
+ if (!(new File (resourceFile).isFile ())) {
+ resourceFile = base + ".__override__/packaged_resources";
+ if (!(new File (resourceFile).isFile ())) {
+ resourceFile = base + ".__override__/resources";
+ if (!(new File (resourceFile).isDirectory ())) {
+ Log.v ("MultiDexLoader", "Cannot find external resources, not patching them in");
+ return null;
+ }
+ }
+ }
+
+ Log.v ("MultiDexLoader", "Found external resources at " + resourceFile);
+ return resourceFile;
+ }
+
+ private List getDexList (String packageName)
+ {
+ List result = new ArrayList ();
+ String dexDirectory = mIncrementalDeploymentDir + ".__override__/dexes";
+ File[] dexes = new File (dexDirectory).listFiles ();
+ // It is not illegal state when it was launched to start Seppuku
+ if (dexes == null) {
+ Log.v("MultiDexLoader", "No dexes!");
+ return null;
+ } else {
+ for (File dex : dexes) {
+ if (dex.getName ().endsWith (".dex")) {
+ Log.v("MultiDexLoader", "Adding dex " + dex.getPath ());
+ result.add (dex.getPath ());
+ }
+ }
+ }
+
+ return result;
+ }
+
+ // ---
+ @Override
+ public android.database.Cursor query (android.net.Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
+ {
+ throw new RuntimeException ("This operation is not supported.");
+ }
+
+ @Override
+ public String getType (android.net.Uri uri)
+ {
+ throw new RuntimeException ("This operation is not supported.");
+ }
+
+ @Override
+ public android.net.Uri insert (android.net.Uri uri, android.content.ContentValues initialValues)
+ {
+ throw new RuntimeException ("This operation is not supported.");
+ }
+
+ @Override
+ public int delete (android.net.Uri uri, String where, String[] whereArgs)
+ {
+ throw new RuntimeException ("This operation is not supported.");
+ }
+
+ @Override
+ public int update (android.net.Uri uri, android.content.ContentValues values, String where, String[] whereArgs)
+ {
+ throw new RuntimeException ("This operation is not supported.");
+ }
+ public static List getActivities(Context context, boolean foregroundOnly)
+ {
+ List list = new ArrayList();
+ try {
+ Class activityThreadClass = Class.forName("android.app.ActivityThread");
+ Object activityThread = MonkeyPatcher.getActivityThread(context, activityThreadClass);
+ Field activitiesField = activityThreadClass.getDeclaredField("mActivities");
+ activitiesField.setAccessible(true);
+ Collection c;
+ Object collection = activitiesField.get(activityThread);
+ if (collection instanceof HashMap) {
+ // Older platforms
+ Map activities = (HashMap) collection;
+ c = activities.values();
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
+ collection instanceof ArrayMap) {
+ ArrayMap activities = (ArrayMap) collection;
+ c = activities.values();
+ } else {
+ return list;
+ }
+ for (Object activityClientRecord : c) {
+ Class activityClientRecordClass = activityClientRecord.getClass();
+ if (foregroundOnly) {
+ Field pausedField = activityClientRecordClass.getDeclaredField("paused");
+ pausedField.setAccessible(true);
+ if (pausedField.getBoolean(activityClientRecord)) {
+ continue;
+ }
+ }
+ Field activityField = activityClientRecordClass.getDeclaredField("activity");
+ activityField.setAccessible(true);
+ Activity activity = (Activity) activityField.get(activityClientRecord);
+ if (activity != null) {
+ list.add(activity);
+ }
+ }
+ } catch (Throwable e) {
+ if (Log.isLoggable("MultiDexLoader", Log.WARN)) {
+ Log.w("MultiDexLoader", "Error retrieving activities", e);
+ }
+ }
+ return list;
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Resources/MultiDexLoader.java b/src/Xamarin.Android.Build.Tasks/Resources/MultiDexLoader.java
new file mode 100644
index 00000000000..8a12ae3f8f3
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Resources/MultiDexLoader.java
@@ -0,0 +1,126 @@
+//
+// This is a modified version of Bazel source code. Its copyright lines follow below.
+//
+
+// Copyright 2015 Xamarin Inc. All rights reserved.
+
+//
+// Copyright 2014 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package mono.android;
+
+import mono.android.incrementaldeployment.IncrementalClassLoader;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Collection;
+import android.app.Application;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import android.content.*;
+import android.util.Log;
+import android.os.Build;
+import dalvik.system.BaseDexClassLoader;
+
+public class MultiDexLoader extends ContentProvider {
+
+ @Override
+ public boolean onCreate ()
+ {
+ return true;
+ }
+
+ @Override
+ public void attachInfo (android.content.Context context, android.content.pm.ProviderInfo info) {
+
+ File codeCacheDir = context.getCacheDir ();
+ String nativeLibDir = context.getApplicationInfo ().nativeLibraryDir;
+ String dataDir = context.getApplicationInfo ().dataDir;
+ String packageName = context.getPackageName ();
+
+ List dexes = getDexList (packageName);
+ if (dexes != null && dexes.size () > 0) {
+ IncrementalClassLoader.inject (
+ MultiDexLoader.class.getClassLoader (),
+ packageName,
+ codeCacheDir,
+ nativeLibDir,
+ dexes);
+ }
+ super.attachInfo (context, info);
+ }
+
+ private List getDexList (String packageName) {
+ List result = new ArrayList ();
+ return result;
+ }
+
+ // ---
+ @Override
+ public android.database.Cursor query (android.net.Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
+ {
+ throw new RuntimeException ("This operation is not supported.");
+ }
+
+ @Override
+ public String getType (android.net.Uri uri)
+ {
+ throw new RuntimeException ("This operation is not supported.");
+ }
+
+ @Override
+ public android.net.Uri insert (android.net.Uri uri, android.content.ContentValues initialValues)
+ {
+ throw new RuntimeException ("This operation is not supported.");
+ }
+
+ @Override
+ public int delete (android.net.Uri uri, String where, String[] whereArgs)
+ {
+ throw new RuntimeException ("This operation is not supported.");
+ }
+
+ @Override
+ public int update (android.net.Uri uri, android.content.ContentValues values, String where, String[] whereArgs)
+ {
+ throw new RuntimeException ("This operation is not supported.");
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
index 377b5d0bb90..25b1cbe438f 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
@@ -189,8 +189,7 @@ void ExecuteWithAbi (string supportedAbis, string apkInputPath, string apkOutput
count = 0;
}
}
- if (StubApplicationDataFile != null && File.Exists (StubApplicationDataFile))
- apk.Archive.AddFile (StubApplicationDataFile, Path.GetFileName (StubApplicationDataFile));
+
}
MonoAndroidHelper.CopyIfZipChanged (apkOutputPath + "new", apkOutputPath);
File.Delete (apkOutputPath + "new");
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CompileToDalvik.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CompileToDalvik.cs
index b4ab6f5965b..087c9aa9f12 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/CompileToDalvik.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/CompileToDalvik.cs
@@ -120,10 +120,9 @@ protected override string GenerateCommandLineCommands ()
} else {
Log.LogDebugMessage (" processing ClassesOutputDirectory...");
var zip = Path.GetFullPath (Path.Combine (ClassesOutputDirectory, "..", "classes.zip"));
- if (!File.Exists (zip)) {
- throw new FileNotFoundException ($"'{zip}' does not exist. Please rebuild the project.");
+ if (File.Exists (zip)) {
+ cmd.AppendFileNameIfNotNull (zip);
}
- cmd.AppendFileNameIfNotNull (zip);
foreach (var jar in JavaLibrariesToCompile)
cmd.AppendFileNameIfNotNull (jar.ItemSpec);
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ComputeHash.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ComputeHash.cs
new file mode 100644
index 00000000000..7f7bd43d8d4
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/ComputeHash.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Security.Cryptography;
+using Microsoft.Build.Utilities;
+using Microsoft.Build.Framework;
+
+namespace Xamarin.Android.Tasks {
+ public class ComputeHash : Task {
+
+ List output = new List ();
+
+ [Required]
+ public ITaskItem [] Source { get; set; }
+
+ [Output]
+ public ITaskItem [] Output { get => output.ToArray (); }
+
+ public override bool Execute ()
+ {
+ Log.LogDebugTaskItems ("Source : ", Source);
+ using (var sha1 = SHA1.Create ()) {
+
+ foreach (var item in Source) {
+ output.Add (new TaskItem (item.ItemSpec, new Dictionary () {
+ { "Hash", HashItemSpec (sha1, item.ItemSpec) }
+ }));
+ }
+ Log.LogDebugTaskItems ("Output : ", Output);
+ return !Log.HasLoggedErrors;
+ }
+ }
+
+ string HashItemSpec (SHA1 sha1, string hashInput)
+ {
+ var hash = sha1.ComputeHash (Encoding.UTF8.GetBytes (hashInput));
+ var hashResult = new StringBuilder (hash.Length * 2);
+
+ foreach (byte b in hash) {
+ hashResult.Append (b.ToString ("x2"));
+ }
+ return hashResult.ToString ();
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateMultiDexMainDexClassList.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateMultiDexMainDexClassList.cs
index 856112ed783..a138f28a91c 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/CreateMultiDexMainDexClassList.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateMultiDexMainDexClassList.cs
@@ -47,12 +47,6 @@ public override bool Execute ()
Log.LogDebugMessage (" ProguardJarPath: {0}", ProguardJarPath);
Log.LogDebugMessage (" ProguardInputJarFilter: {0}", ProguardInputJarFilter);
- if (CustomMainDexListFiles != null && CustomMainDexListFiles.Any ()) {
- var content = string.Concat (CustomMainDexListFiles.Select (i => File.ReadAllText (i.ItemSpec)));
- File.WriteAllText (MultiDexMainDexListFile, content);
- return true;
- }
-
tempJar = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName () + ".jar");
commandlineAction = GenerateProguardCommands;
// run proguard first
@@ -63,7 +57,17 @@ public override bool Execute ()
commandlineAction = GenerateMainDexListBuilderCommands;
// run java second
- return base.Execute () && !Log.HasLoggedErrors;
+ if (File.Exists (MultiDexMainDexListFile))
+ File.WriteAllText (MultiDexMainDexListFile, string.Empty);
+
+ var result = base.Execute () && !Log.HasLoggedErrors;
+
+ if (result && CustomMainDexListFiles != null && CustomMainDexListFiles.Any (x => File.Exists (x.ItemSpec))) {
+ foreach (var content in CustomMainDexListFiles.Select (i => File.ReadAllLines (i.ItemSpec)))
+ File.AppendAllLines (MultiDexMainDexListFile, content);
+ }
+
+ return result;
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
index 6ae7bf0b353..0a4c8d648b2 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
@@ -40,6 +40,7 @@ public class GenerateJavaStubs : Task
public string[] MergedManifestDocuments { get; set; }
public bool Debug { get; set; }
+ public bool MultiDex { get; set; }
public string ApplicationName { get; set; }
public string PackageName { get; set; }
public string [] ManifestPlaceholders { get; set; }
@@ -71,6 +72,7 @@ public override bool Execute ()
Log.LogDebugMessage ("GenerateJavaStubs Task");
Log.LogDebugMessage (" ManifestTemplate: {0}", ManifestTemplate);
Log.LogDebugMessage (" Debug: {0}", Debug);
+ Log.LogDebugMessage (" MultiDex: {0}", MultiDex);
Log.LogDebugMessage (" ApplicationName: {0}", ApplicationName);
Log.LogDebugMessage (" PackageName: {0}", PackageName);
Log.LogDebugMessage (" AndroidSdkDir: {0}", AndroidSdkDir);
@@ -231,6 +233,7 @@ void Run (DirectoryAssemblyResolver res)
manifest.SdkDir = AndroidSdkDir;
manifest.SdkVersion = AndroidSdkPlatform;
manifest.Debug = Debug;
+ manifest.MultiDex = MultiDex;
manifest.NeedsInternet = NeedsInternet;
var additionalProviders = manifest.Merge (all_java_types, selectedWhitelistAssemblies, ApplicationJavaClass, EmbedAssemblies, BundledWearApplicationName, MergedManifestDocuments);
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs
index aaa387da0fc..12119bce312 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs
@@ -778,6 +778,17 @@ public void BuildAfterMultiDexIsNotRequired ()
[Test]
public void MultiDexCustomMainDexFileList ()
{
+ var expected = @"android/support/multidex/ZipUtil$CentralDirectory.class
+android/support/multidex/MultiDexApplication.class
+android/support/multidex/MultiDex$V19.class
+android/support/multidex/MultiDex$V4.class
+android/support/multidex/ZipUtil.class
+android/support/multidex/MultiDexExtractor$1.class
+android/support/multidex/MultiDexExtractor.class
+android/support/multidex/MultiDex$V14.class
+android/support/multidex/MultiDex.class
+MyTest
+";
var proj = CreateMultiDexRequiredApplication ();
proj.SetProperty ("AndroidEnableMultiDex", "True");
proj.OtherBuildItems.Add (new BuildItem ("MultiDexMainDexList", "mymultidex.keep") { TextContent = () => "MyTest", Encoding = Encoding.ASCII });
@@ -785,7 +796,10 @@ public void MultiDexCustomMainDexFileList ()
var b = CreateApkBuilder ("temp/MultiDexCustomMainDexFileList");
b.ThrowOnBuildFailure = false;
Assert.IsTrue (b.Build (proj), "build should succeed. Run will fail.");
- Assert.AreEqual ("MyTest", File.ReadAllText (Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, "multidex.keep")), "unexpected multidex.keep content");
+ var data = File.ReadAllText (Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, "multidex.keep"));
+ data = Regex.Replace (data, @"\r\n|\n\r|\n|\r", "\r\n");
+ expected = Regex.Replace (expected, @"\r\n|\n\r|\n|\r", "\r\n");
+ Assert.AreEqual (expected, data, "unexpected multidex.keep content");
b.Clean (proj);
b.Dispose ();
}
@@ -2828,6 +2842,7 @@ public void BuildAMassiveApp()
Verbosity = LoggerVerbosity.Diagnostic,
};
var app1 = new XamarinAndroidApplicationProject() {
+ TargetFrameworkVersion = sb.LatestTargetFrameworkVersion (),
ProjectName = "App1",
AotAssemblies = true,
IsRelease = true,
@@ -2868,6 +2883,7 @@ public void BuildAMassiveApp()
for (int i = 0; i < 128; i++) {
var libName = $"Lib{i}";
var lib = new XamarinAndroidLibraryProject() {
+ TargetFrameworkVersion = sb.LatestTargetFrameworkVersion (),
ProjectName = libName,
IsRelease = true,
OtherBuildItems = {
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
index 582c60f5d94..76b52c14466 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
@@ -84,6 +84,7 @@ internal class ManifestDocument
public string SdkDir { get; set; }
public string SdkVersion { get; set; }
public bool Debug { get; set; }
+ public bool MultiDex { get; set; }
public bool NeedsInternet { get; set; }
public string VersionCode {
get {
@@ -355,6 +356,9 @@ public IList Merge (List subclasses, List select
var providerNames = AddMonoRuntimeProviders (app);
+ if (MultiDex)
+ app.Add (CreateMonoRuntimeProvider ("mono.android.MultiDexLoader", null, initOrder: ++AppInitOrder));
+
if (Debug) {
app.Add (new XComment ("suppress ExportedReceiver"));
app.Add (new XElement ("receiver",
@@ -583,7 +587,7 @@ XElement CreateApplicationElement (XElement manifest, string applicationClass, L
IList AddMonoRuntimeProviders (XElement app)
{
- app.Add (CreateMonoRuntimeProvider ("mono.MonoRuntimeProvider", null));
+ app.Add (CreateMonoRuntimeProvider ("mono.MonoRuntimeProvider", null, AppInitOrder));
var providerNames = new List ();
@@ -609,7 +613,7 @@ IList AddMonoRuntimeProviders (XElement app)
case "service":
string providerName = "MonoRuntimeProvider_" + procs.Count;
providerNames.Add (providerName);
- app.Add (CreateMonoRuntimeProvider ("mono." + providerName, proc.Value));
+ app.Add (CreateMonoRuntimeProvider ("mono." + providerName, proc.Value, ++AppInitOrder));
break;
}
}
@@ -617,12 +621,14 @@ IList AddMonoRuntimeProviders (XElement app)
return providerNames;
}
- XElement CreateMonoRuntimeProvider (string name, string processName)
+ int AppInitOrder = 2000000000;
+
+ XElement CreateMonoRuntimeProvider (string name, string processName, int initOrder)
{
return new XElement ("provider",
new XAttribute (androidNs + "name", name),
new XAttribute (androidNs + "exported", "false"),
- new XAttribute (androidNs + "initOrder", int.MaxValue.ToString ()),
+ new XAttribute (androidNs + "initOrder", initOrder),
processName == null ? null : new XAttribute (androidNs + "process", processName),
new XAttribute (androidNs + "authorities", PackageName + "." + name + ".__mono_init__"));
}
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
index b679aa6529e..8a243ad8d0e 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
@@ -251,6 +251,7 @@
+
pdb2mdb\BitAccess.cs
@@ -674,6 +675,15 @@
StubApplication.java
+
+ MultiDexLoader.java
+
+
+ MultiDexLoader.Debug.java
+
+
+ MonkeyPatcher.java
+
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
index a5de5858bf7..d0375a72245 100755
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
@@ -41,6 +41,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
+
@@ -1940,6 +1941,14 @@ because xbuild doesn't support framework reference assemblies.
ResourceName="Seppuku.java"
OutputPath="$(MonoAndroidIntermediate)android\src\mono\android\Seppuku.java"
Condition="'$(AndroidIncludeDebugSymbols)' == 'True'" />
+
+
@@ -2425,6 +2435,7 @@ because xbuild doesn't support framework reference assemblies.
_GeneratePackageManagerJava;
_FindJavaStubFiles;
_AddStaticResources;
+ $(_AfterAddStaticResources);
_GetMonoPlatformJarPath;
$(_OnResolveMonoAndroidSdks);
_GetLibraryImports;
@@ -2505,7 +2516,7 @@ because xbuild doesn't support framework reference assemblies.
@@ -2623,7 +2634,6 @@ because xbuild doesn't support framework reference assemblies.
<_DexFile Include="$(IntermediateOutputPath)android\bin\dex\*.dex" />
<_DexFile Include="$(IntermediateOutputPath)android\bin\*.dex" />
- <_AndroidNativeLibraryForFastDev Include="@(AndroidNativeLibrary)" />
@@ -3098,6 +3108,7 @@ because xbuild doesn't support framework reference assemblies.
+
diff --git a/src/monodroid/jni/monodroid-glue.c b/src/monodroid/jni/monodroid-glue.c
index aaefe486e60..26b91edbd30 100644
--- a/src/monodroid/jni/monodroid-glue.c
+++ b/src/monodroid/jni/monodroid-glue.c
@@ -605,32 +605,6 @@ readdir_r (_WDIR *dirp, struct _wdirent *entry, struct _wdirent **result)
#endif // def WINDOWS
-#ifndef RELEASE
-
-static void
-copy_monosgen_to_internal_location(char *to, char *from)
-{
- char *from_libmonoso = path_combine (from, "libmonosgen-2.0.so");
-
- if (!file_exists (from_libmonoso))
- {
- free (from_libmonoso);
- return;
- }
-
- log_warn (LOG_DEFAULT, "Copying sgen from external location %s to internal location %s", from, to);
-
- char *to_libmonoso = path_combine (to, "libmonosgen-2.0.so");
- unlink (to_libmonoso);
-
- if (file_copy (to_libmonoso, from_libmonoso) < 0)
- log_warn (LOG_DEFAULT, "Copy failed: %s", strerror (errno));
-
- free (from_libmonoso);
- free (to_libmonoso);
-}
-#endif
-
#if ANDROID || LINUX
#define MONO_SGEN_SO "libmonosgen-2.0.so"
#define MONO_SGEN_ARCH_SO "libmonosgen-%s-2.0.so"
@@ -654,6 +628,46 @@ copy_monosgen_to_internal_location(char *to, char *from)
free (libmonoso); \
}
+static void
+copy_file_to_internal_location(char *to_dir, char *from_dir, char* file);
+
+#ifndef RELEASE
+static void
+copy_native_libraries_to_internal_location (void)
+{
+ int i;
+
+ for (i = 0; i < MAX_OVERRIDES; ++i) {
+ monodroid_dir_t *dir;
+ monodroid_dirent_t b, *e;
+
+ const char *dir_path = path_combine (override_dirs [i], "lib");
+ log_warn (LOG_DEFAULT, "checking directory: `%s`", dir_path);
+
+ if (dir_path == NULL || !directory_exists (dir_path)) {
+ log_warn (LOG_DEFAULT, "directory does not exist: `%s`", dir_path);
+ free (dir_path);
+ continue;
+ }
+
+ if ((dir = monodroid_opendir (dir_path)) == NULL) {
+ log_warn (LOG_DEFAULT, "could not open directory: `%s`", dir_path);
+ free (dir_path);
+ continue;
+ }
+
+ while (readdir_r (dir, &b, &e) == 0 && e) {
+ log_warn (LOG_DEFAULT, "checking file: `%s`", e->d_name);
+ if (monodroid_dirent_hasextension (e, ".so")) {
+ copy_file_to_internal_location (primary_override_dir, dir_path, e->d_name);
+ }
+ }
+ monodroid_closedir (dir);
+ free (dir_path);
+ }
+}
+#endif
+
static char*
get_libmonosgen_path ()
{
@@ -664,8 +678,7 @@ get_libmonosgen_path ()
// Android 5 includes some restrictions on loading dynamic libraries via dlopen() from
// external storage locations so we need to file copy the shared object to an internal
// storage location before loading it.
- copy_monosgen_to_internal_location (primary_override_dir, external_override_dir);
- copy_monosgen_to_internal_location (primary_override_dir, external_legacy_override_dir);
+ copy_native_libraries_to_internal_location ();
if (!embedded_dso_mode) {
for (i = 0; i < MAX_OVERRIDES; ++i)
@@ -2641,39 +2654,49 @@ static void
set_user_executable (const char *path)
{
int r;
- do
+ do {
r = chmod (path, S_IRUSR | S_IWUSR | S_IXUSR);
- while (r == -1 && errno == EINTR);
+ } while (r == -1 && errno == EINTR);
if (r == -1)
log_error (LOG_DEFAULT, "chmod(\"%s\") failed: %s", path, strerror (errno));
}
static void
-copy_file_to_internal_location(char *to, char *from, char* file)
+copy_file_to_internal_location(char *to_dir, char *from_dir, char* file)
{
- char *from_file = path_combine (from, file);
-
- if (!file_exists (from_file))
- {
- free (from_file);
- return;
- }
-
- log_warn (LOG_DEFAULT, "Copying file %s from external location %s to internal location %s",
- file, from, to);
-
- char *to_file = path_combine (to, file);
- unlink (to_file);
-
- if (file_copy (to_file, from_file) < 0)
- log_warn (LOG_DEFAULT, "Copy failed: %s", strerror (errno));
-
- set_user_executable (to_file);
+ char *from_file = path_combine (from_dir, file);
+ char *to_file = NULL;
+
+ do {
+ if (!from_file || !file_exists (from_file))
+ break;
+ log_warn (LOG_DEFAULT, "Copying file `%s` from external location `%s` to internal location `%s`",
+ file, from_dir, to_dir);
+
+ to_file = path_combine (to_dir, file);
+ if (!to_file)
+ break;
+
+ int r = unlink (to_file);
+ if (r < 0 && errno != ENOENT) {
+ log_warn (LOG_DEFAULT, "Unable to delete file `%s`: %s", to_file, strerror (errno));
+ break;
+ }
+
+ if (file_copy (to_file, from_file) < 0) {
+ log_warn (LOG_DEFAULT, "Copy failed from `%s` to `%s`: %s", from_file, to_file, strerror (errno));
+ break;
+ }
+
+ set_user_executable (to_file);
+ } while (0);
+
free (from_file);
free (to_file);
}
+
#else /* !defined (ANDROID) */
#ifdef DEBUG
static int
@@ -2687,6 +2710,11 @@ void
set_world_accessable (const char *path)
{
}
+
+static void
+copy_file_to_internal_location(char *to_dir, char *from_dir, char* file)
+{
+}
#endif /* !defined (ANDROID) */
static void
@@ -3952,6 +3980,7 @@ Java_mono_android_Runtime_init (JNIEnv *env, jclass klass, jstring lang, jobject
rd = (*env)->GetStringUTFChars (env, runtimeNativeLibDir, NULL);
runtime_libdir = monodroid_strdup_printf ("%s", rd);
(*env)->ReleaseStringUTFChars (env, runtimeNativeLibDir, rd);
+ log_warn (LOG_DEFAULT, "Using runtime path: %s", runtime_libdir);
}
void *libmonosgen_handle = NULL;
@@ -3972,6 +4001,7 @@ Java_mono_android_Runtime_init (JNIEnv *env, jclass klass, jstring lang, jobject
log_fatal (LOG_DEFAULT, "shared runtime initialization error: %s", dlerror ());
exit (FATAL_EXIT_CANNOT_FIND_MONO);
}
+ log_warn (LOG_DEFAULT, "Loaded monosgen from : %s", get_libmonosgen_path ());
setup_process_args (env, runtimeApks);
#ifndef WINDOWS
_monodroid_getifaddrs_init ();
diff --git a/tests/RunApkTests.targets b/tests/RunApkTests.targets
index 16ab972e95b..4832439cd1b 100644
--- a/tests/RunApkTests.targets
+++ b/tests/RunApkTests.targets
@@ -21,6 +21,7 @@
+
diff --git a/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj b/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj
new file mode 100644
index 00000000000..6814e784536
--- /dev/null
+++ b/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj
@@ -0,0 +1,102 @@
+
+
+
+ Debug
+ AnyCPU
+ 10.0.0
+ 2.0
+ {9ECBEA14-B79F-4F92-9266-495C03A32571}
+ {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ Library
+ Xamarin.Android.RuntimeTests
+ True
+ Resources\Resource.designer.cs
+ Resource
+ Resources
+ Assets
+ Mono.Android-TestsMultiDex
+ Properties\AndroidManifest.xml
+ armeabi-v7a;x86
+ False
+ false
+ true
+
+
+
+
+ $(AndroidFrameworkVersion)
+
+
+ true
+ full
+ false
+ ..\..\bin\TestDebug
+ DEBUG;
+ prompt
+ 4
+ None
+ false
+ true
+
+
+ true
+ ..\..\bin\TestRelease
+ prompt
+ 4
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Resources\Drawable\Image.9.png
+
+
+
+
+
+
+ {3cc4e384-4985-4d93-a34c-73f69a379fa7}
+ TestRunner.Core
+
+
+ {CB2335CB-0050-4020-8A05-E9614EDAA05E}
+ TestRunner.NUnit
+
+
+ Xamarin.Android.Build.Tasks
+ {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}
+ False
+ False
+
+
+ Xamarin.Android.NUnitLite
+ {4D603AA3-3BFD-43C8-8050-0CD6C2601126}
+ False
+ False
+
+
+
+
+
+
diff --git a/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.projitems b/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.projitems
new file mode 100644
index 00000000000..06e7fd04f2e
--- /dev/null
+++ b/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.projitems
@@ -0,0 +1,15 @@
+
+
+
+
+ Mono.Android_TestsMultiDex
+ xamarin.android.runtimetests.TestInstrumentation
+ $(MSBuildThisFileDirectory)..\..\TestResult-Mono.Android_TestsMultiDex.xml
+ $(MSBuildThisFileDirectory)..\..\build-tools\scripts\TimingDefinitions.txt
+ $(MSBuildThisFileDirectory)..\..\TestResult-Mono.Android_TestsMultiDex-times.csv
+ apk-sizes-$(_MonoAndroidTestPackage)-$(Configuration)$(TestsAotName).txt
+ $(MSBuildThisFileDirectory)apk-sizes-definitions.txt
+ $(MSBuildThisFileDirectory)..\..\TestResult-Mono.Android_TestsMultDex-values.csv
+
+
+
diff --git a/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.targets b/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.targets
new file mode 100644
index 00000000000..54d93bc272c
--- /dev/null
+++ b/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.targets
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Runtime-MultiDex/Properties/AndroidManifest.xml b/tests/Runtime-MultiDex/Properties/AndroidManifest.xml
new file mode 100644
index 00000000000..dd3b0453087
--- /dev/null
+++ b/tests/Runtime-MultiDex/Properties/AndroidManifest.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
diff --git a/tests/Runtime-MultiDex/Resources/AboutResources.txt b/tests/Runtime-MultiDex/Resources/AboutResources.txt
new file mode 100644
index 00000000000..10f52d46021
--- /dev/null
+++ b/tests/Runtime-MultiDex/Resources/AboutResources.txt
@@ -0,0 +1,44 @@
+Images, layout descriptions, binary blobs and string dictionaries can be included
+in your application as resource files. Various Android APIs are designed to
+operate on the resource IDs instead of dealing with images, strings or binary blobs
+directly.
+
+For example, a sample Android app that contains a user interface layout (main.axml),
+an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
+would keep its resources in the "Resources" directory of the application:
+
+Resources/
+ drawable/
+ icon.png
+
+ layout/
+ main.axml
+
+ values/
+ strings.xml
+
+In order to get the build system to recognize Android resources, set the build action to
+"AndroidResource". The native Android APIs do not operate directly with filenames, but
+instead operate on resource IDs. When you compile an Android application that uses resources,
+the build system will package the resources for distribution and generate a class called "R"
+(this is an Android convention) that contains the tokens for each one of the resources
+included. For example, for the above Resources layout, this is what the R class would expose:
+
+public class R {
+ public class drawable {
+ public const int icon = 0x123;
+ }
+
+ public class layout {
+ public const int main = 0x456;
+ }
+
+ public class strings {
+ public const int first_string = 0xabc;
+ public const int second_string = 0xbcd;
+ }
+}
+
+You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main
+to reference the layout/main.axml file, or R.strings.first_string to reference the first
+string in the dictionary file values/strings.xml.
diff --git a/tests/Runtime-MultiDex/Resources/Resource.designer.cs b/tests/Runtime-MultiDex/Resources/Resource.designer.cs
new file mode 100644
index 00000000000..5687804a898
--- /dev/null
+++ b/tests/Runtime-MultiDex/Resources/Resource.designer.cs
@@ -0,0 +1,270 @@
+#pragma warning disable 1591
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+[assembly: global::Android.Runtime.ResourceDesignerAttribute("Xamarin.Android.RuntimeTests.Resource", IsApplication=true)]
+
+namespace Xamarin.Android.RuntimeTests
+{
+
+
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
+ public partial class Resource
+ {
+
+ static Resource()
+ {
+ global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+ }
+
+ public static void UpdateIdValues()
+ {
+ global::Xamarin.Android.UnitTests.Resource.String.library_name = global::Xamarin.Android.RuntimeTests.Resource.String.library_name;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.OptionHostName = global::Xamarin.Android.RuntimeTests.Resource.Id.OptionHostName;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.OptionPort = global::Xamarin.Android.RuntimeTests.Resource.Id.OptionPort;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.OptionRemoteServer = global::Xamarin.Android.RuntimeTests.Resource.Id.OptionRemoteServer;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.OptionsButton = global::Xamarin.Android.RuntimeTests.Resource.Id.OptionsButton;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.ResultFullName = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultFullName;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.ResultMessage = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultMessage;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.ResultResultState = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultResultState;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.ResultRunSingleMethodTest = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultRunSingleMethodTest;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.ResultStackTrace = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultStackTrace;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.ResultsFailed = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsFailed;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.ResultsId = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsId;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.ResultsIgnored = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsIgnored;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.ResultsInconclusive = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsInconclusive;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.ResultsMessage = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsMessage;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.ResultsPassed = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsPassed;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.ResultsResult = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsResult;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.RunTestsButton = global::Xamarin.Android.RuntimeTests.Resource.Id.RunTestsButton;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Id.TestSuiteListView = global::Xamarin.Android.RuntimeTests.Resource.Id.TestSuiteListView;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Layout.options = global::Xamarin.Android.RuntimeTests.Resource.Layout.options;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Layout.results = global::Xamarin.Android.RuntimeTests.Resource.Layout.results;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Layout.test_result = global::Xamarin.Android.RuntimeTests.Resource.Layout.test_result;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.Layout.test_suite = global::Xamarin.Android.RuntimeTests.Resource.Layout.test_suite;
+ global::Xamarin.Android.UnitTests.NUnit.Resource.String.library_name = global::Xamarin.Android.RuntimeTests.Resource.String.library_name;
+ global::Xamarin.Android.NUnitLite.Resource.Id.OptionHostName = global::Xamarin.Android.RuntimeTests.Resource.Id.OptionHostName;
+ global::Xamarin.Android.NUnitLite.Resource.Id.OptionPort = global::Xamarin.Android.RuntimeTests.Resource.Id.OptionPort;
+ global::Xamarin.Android.NUnitLite.Resource.Id.OptionRemoteServer = global::Xamarin.Android.RuntimeTests.Resource.Id.OptionRemoteServer;
+ global::Xamarin.Android.NUnitLite.Resource.Id.OptionsButton = global::Xamarin.Android.RuntimeTests.Resource.Id.OptionsButton;
+ global::Xamarin.Android.NUnitLite.Resource.Id.ResultFullName = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultFullName;
+ global::Xamarin.Android.NUnitLite.Resource.Id.ResultMessage = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultMessage;
+ global::Xamarin.Android.NUnitLite.Resource.Id.ResultResultState = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultResultState;
+ global::Xamarin.Android.NUnitLite.Resource.Id.ResultRunSingleMethodTest = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultRunSingleMethodTest;
+ global::Xamarin.Android.NUnitLite.Resource.Id.ResultStackTrace = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultStackTrace;
+ global::Xamarin.Android.NUnitLite.Resource.Id.ResultsFailed = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsFailed;
+ global::Xamarin.Android.NUnitLite.Resource.Id.ResultsId = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsId;
+ global::Xamarin.Android.NUnitLite.Resource.Id.ResultsIgnored = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsIgnored;
+ global::Xamarin.Android.NUnitLite.Resource.Id.ResultsInconclusive = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsInconclusive;
+ global::Xamarin.Android.NUnitLite.Resource.Id.ResultsMessage = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsMessage;
+ global::Xamarin.Android.NUnitLite.Resource.Id.ResultsPassed = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsPassed;
+ global::Xamarin.Android.NUnitLite.Resource.Id.ResultsResult = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsResult;
+ global::Xamarin.Android.NUnitLite.Resource.Id.RunTestsButton = global::Xamarin.Android.RuntimeTests.Resource.Id.RunTestsButton;
+ global::Xamarin.Android.NUnitLite.Resource.Id.TestSuiteListView = global::Xamarin.Android.RuntimeTests.Resource.Id.TestSuiteListView;
+ global::Xamarin.Android.NUnitLite.Resource.Layout.options = global::Xamarin.Android.RuntimeTests.Resource.Layout.options;
+ global::Xamarin.Android.NUnitLite.Resource.Layout.results = global::Xamarin.Android.RuntimeTests.Resource.Layout.results;
+ global::Xamarin.Android.NUnitLite.Resource.Layout.test_result = global::Xamarin.Android.RuntimeTests.Resource.Layout.test_result;
+ global::Xamarin.Android.NUnitLite.Resource.Layout.test_suite = global::Xamarin.Android.RuntimeTests.Resource.Layout.test_suite;
+ }
+
+ public partial class Attribute
+ {
+
+ static Attribute()
+ {
+ global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+ }
+
+ private Attribute()
+ {
+ }
+ }
+
+ public partial class Drawable
+ {
+
+ // aapt resource value: 0x7f020000
+ public const int android_button = 2130837504;
+
+ // aapt resource value: 0x7f020001
+ public const int android_focused = 2130837505;
+
+ // aapt resource value: 0x7f020002
+ public const int android_normal = 2130837506;
+
+ // aapt resource value: 0x7f020003
+ public const int AndroidPressed = 2130837507;
+
+ // aapt resource value: 0x7f020004
+ public const int Icon = 2130837508;
+
+ static Drawable()
+ {
+ global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+ }
+
+ private Drawable()
+ {
+ }
+ }
+
+ public partial class Id
+ {
+
+ // aapt resource value: 0x7f060008
+ public const int OptionHostName = 2131099656;
+
+ // aapt resource value: 0x7f060009
+ public const int OptionPort = 2131099657;
+
+ // aapt resource value: 0x7f060007
+ public const int OptionRemoteServer = 2131099655;
+
+ // aapt resource value: 0x7f060017
+ public const int OptionsButton = 2131099671;
+
+ // aapt resource value: 0x7f060012
+ public const int ResultFullName = 2131099666;
+
+ // aapt resource value: 0x7f060014
+ public const int ResultMessage = 2131099668;
+
+ // aapt resource value: 0x7f060013
+ public const int ResultResultState = 2131099667;
+
+ // aapt resource value: 0x7f060011
+ public const int ResultRunSingleMethodTest = 2131099665;
+
+ // aapt resource value: 0x7f060015
+ public const int ResultStackTrace = 2131099669;
+
+ // aapt resource value: 0x7f06000d
+ public const int ResultsFailed = 2131099661;
+
+ // aapt resource value: 0x7f06000a
+ public const int ResultsId = 2131099658;
+
+ // aapt resource value: 0x7f06000e
+ public const int ResultsIgnored = 2131099662;
+
+ // aapt resource value: 0x7f06000f
+ public const int ResultsInconclusive = 2131099663;
+
+ // aapt resource value: 0x7f060010
+ public const int ResultsMessage = 2131099664;
+
+ // aapt resource value: 0x7f06000c
+ public const int ResultsPassed = 2131099660;
+
+ // aapt resource value: 0x7f06000b
+ public const int ResultsResult = 2131099659;
+
+ // aapt resource value: 0x7f060016
+ public const int RunTestsButton = 2131099670;
+
+ // aapt resource value: 0x7f060018
+ public const int TestSuiteListView = 2131099672;
+
+ // aapt resource value: 0x7f060003
+ public const int csharp_full_assembly = 2131099651;
+
+ // aapt resource value: 0x7f060001
+ public const int csharp_legacy_fragment = 2131099649;
+
+ // aapt resource value: 0x7f060002
+ public const int csharp_partial_assembly = 2131099650;
+
+ // aapt resource value: 0x7f060000
+ public const int csharp_simple_fragment = 2131099648;
+
+ // aapt resource value: 0x7f060004
+ public const int first_text_view = 2131099652;
+
+ // aapt resource value: 0x7f060006
+ public const int my_scroll_view = 2131099654;
+
+ // aapt resource value: 0x7f060005
+ public const int second_text_view = 2131099653;
+
+ static Id()
+ {
+ global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+ }
+
+ private Id()
+ {
+ }
+ }
+
+ public partial class Layout
+ {
+
+ // aapt resource value: 0x7f030000
+ public const int FragmentFixup = 2130903040;
+
+ // aapt resource value: 0x7f030001
+ public const int Main = 2130903041;
+
+ // aapt resource value: 0x7f030002
+ public const int options = 2130903042;
+
+ // aapt resource value: 0x7f030003
+ public const int results = 2130903043;
+
+ // aapt resource value: 0x7f030004
+ public const int test_result = 2130903044;
+
+ // aapt resource value: 0x7f030005
+ public const int test_suite = 2130903045;
+
+ static Layout()
+ {
+ global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+ }
+
+ private Layout()
+ {
+ }
+ }
+
+ public partial class String
+ {
+
+ // aapt resource value: 0x7f050000
+ public const int library_name = 2131034112;
+
+ static String()
+ {
+ global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+ }
+
+ private String()
+ {
+ }
+ }
+
+ public partial class Xml
+ {
+
+ // aapt resource value: 0x7f040000
+ public const int XmlReaderResourceParser = 2130968576;
+
+ static Xml()
+ {
+ global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+ }
+
+ private Xml()
+ {
+ }
+ }
+ }
+}
+#pragma warning restore 1591
diff --git a/tests/Runtime-MultiDex/Resources/color/WhiterShadeOfPale.xml b/tests/Runtime-MultiDex/Resources/color/WhiterShadeOfPale.xml
new file mode 100644
index 00000000000..a5bf8da09f2
--- /dev/null
+++ b/tests/Runtime-MultiDex/Resources/color/WhiterShadeOfPale.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/tests/Runtime-MultiDex/Resources/drawable/AndroidPressed.png b/tests/Runtime-MultiDex/Resources/drawable/AndroidPressed.png
new file mode 100644
index 00000000000..fe81ff9e279
Binary files /dev/null and b/tests/Runtime-MultiDex/Resources/drawable/AndroidPressed.png differ
diff --git a/tests/Runtime-MultiDex/Resources/drawable/Icon.png b/tests/Runtime-MultiDex/Resources/drawable/Icon.png
new file mode 100644
index 00000000000..a07c69fa5a0
Binary files /dev/null and b/tests/Runtime-MultiDex/Resources/drawable/Icon.png differ
diff --git a/tests/Runtime-MultiDex/Resources/drawable/android_button.xml b/tests/Runtime-MultiDex/Resources/drawable/android_button.xml
new file mode 100644
index 00000000000..85d525f4787
--- /dev/null
+++ b/tests/Runtime-MultiDex/Resources/drawable/android_button.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/tests/Runtime-MultiDex/Resources/drawable/android_focused.png b/tests/Runtime-MultiDex/Resources/drawable/android_focused.png
new file mode 100644
index 00000000000..f84d0fe4a5e
Binary files /dev/null and b/tests/Runtime-MultiDex/Resources/drawable/android_focused.png differ
diff --git a/tests/Runtime-MultiDex/Resources/drawable/android_normal.png b/tests/Runtime-MultiDex/Resources/drawable/android_normal.png
new file mode 100644
index 00000000000..94a70842530
Binary files /dev/null and b/tests/Runtime-MultiDex/Resources/drawable/android_normal.png differ
diff --git a/tests/Runtime-MultiDex/Resources/layout/FragmentFixup.axml b/tests/Runtime-MultiDex/Resources/layout/FragmentFixup.axml
new file mode 100644
index 00000000000..51c6dbaeb93
--- /dev/null
+++ b/tests/Runtime-MultiDex/Resources/layout/FragmentFixup.axml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
diff --git a/tests/Runtime-MultiDex/Resources/layout/Main.axml b/tests/Runtime-MultiDex/Resources/layout/Main.axml
new file mode 100644
index 00000000000..da6d4e98aec
--- /dev/null
+++ b/tests/Runtime-MultiDex/Resources/layout/Main.axml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Runtime-MultiDex/Resources/layout/NameFixups.axml b/tests/Runtime-MultiDex/Resources/layout/NameFixups.axml
new file mode 100644
index 00000000000..ba6a000ff55
--- /dev/null
+++ b/tests/Runtime-MultiDex/Resources/layout/NameFixups.axml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Runtime-MultiDex/Resources/values/Strings.xml b/tests/Runtime-MultiDex/Resources/values/Strings.xml
new file mode 100644
index 00000000000..2b362e2a776
--- /dev/null
+++ b/tests/Runtime-MultiDex/Resources/values/Strings.xml
@@ -0,0 +1,5 @@
+
+
+ Hello World, Click Me!
+ Xamarin.Android.ResourceCaseSensitivity
+
diff --git a/tests/Runtime-MultiDex/Resources/values/colors.xml b/tests/Runtime-MultiDex/Resources/values/colors.xml
new file mode 100644
index 00000000000..b2ff9fa5b95
--- /dev/null
+++ b/tests/Runtime-MultiDex/Resources/values/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #aaaaaa
+
\ No newline at end of file
diff --git a/tests/Runtime-MultiDex/Resources/values/theme.axml b/tests/Runtime-MultiDex/Resources/values/theme.axml
new file mode 100644
index 00000000000..d52b7e49920
--- /dev/null
+++ b/tests/Runtime-MultiDex/Resources/values/theme.axml
@@ -0,0 +1,4 @@
+
+
+ #aaaaaa
+
\ No newline at end of file
diff --git a/tests/Runtime-MultiDex/Resources/values/theme.xml b/tests/Runtime-MultiDex/Resources/values/theme.xml
new file mode 100644
index 00000000000..d52b7e49920
--- /dev/null
+++ b/tests/Runtime-MultiDex/Resources/values/theme.xml
@@ -0,0 +1,4 @@
+
+
+ #aaaaaa
+
\ No newline at end of file
diff --git a/tests/Runtime-MultiDex/Resources/xml/XmlReaderResourceParser.xml b/tests/Runtime-MultiDex/Resources/xml/XmlReaderResourceParser.xml
new file mode 100644
index 00000000000..1dc0f3e7485
--- /dev/null
+++ b/tests/Runtime-MultiDex/Resources/xml/XmlReaderResourceParser.xml
@@ -0,0 +1 @@
+
diff --git a/tests/Runtime-MultiDex/apk-sizes-definitions.txt b/tests/Runtime-MultiDex/apk-sizes-definitions.txt
new file mode 100644
index 00000000000..35e88388b72
--- /dev/null
+++ b/tests/Runtime-MultiDex/apk-sizes-definitions.txt
@@ -0,0 +1,6 @@
+apk=^stat: (?\d+)\s+(?.*)$
+Mono.Android.dll=^\s*(?\d+)\s+.*(?Mono.Android.dll)$
+Mono.Android.dll.so=^\s*(?\d+)\s+.*(?Mono.Android.dll.so)$
+mscorlib.dll=^\s*(?\d+)\s+.*(?mscorlib.dll)$
+mscorlib.dll.so=^\s*(?\d+)\s+.*(?mscorlib.dll.so)$
+monosgen-armeabi-v7a=^\s*(?\d+)\s+.*(?armeabi-v7a/libmonosgen-2.0.so)$