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.1true
+ true
diff --git a/src/Xamarin.Android.Build.Tasks.tpnitems b/src/Xamarin.Android.Build.Tasks.tpnitems
index 61c2f7350ce..4de6e1e54fb 100644
--- a/src/Xamarin.Android.Build.Tasks.tpnitems
+++ b/src/Xamarin.Android.Build.Tasks.tpnitems
@@ -6,7 +6,8 @@
https://github.com/bazelbuild/bazel/tree/master/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment
* Xamarin.Android.Build.Tasks/Resources/IncrementalClassLoader.java
* Xamarin.Android.Build.Tasks/Resources/Placeholder.java
- * Xamarin.Android.Build.Tasks/Resources/StubApplication.java
+ * Xamarin.Android.Build.Tasks/Resources/MonkeyPatcher.java
+ * Xamarin.Android.Build.Tasks/Resources/ResourceLoader.java
-->
$(MSBuildThisFileDirectory)\..\build-tools\license-data\Apache-2.0.txt
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..9908d3b67b9
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Resources/MonkeyPatcher.java
@@ -0,0 +1,520 @@
+/*
+ * 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 java.io.File;
+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 {
+ static final int ICE_CREAM_SANDWICH = 14;
+ static final int JELLY_BEAN = 16;
+ static final int JELLY_BEAN_MR2 = 18;
+ static final int KITKAT = 19;
+ static final int LOLLIPOP = 21;
+ static final int M = 23;
+ static final int P = 28;
+
+ @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.
+ if (SDK_INT < P && SDK_INT >= KITKAT) {
+ 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.ge ();
+ 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 >= 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;
+ }
+
+ public 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 + "/";
+ }
+}
\ 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..8a1381044ee 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;
@@ -30,8 +31,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 +46,8 @@ public static void LoadApplication (Context context, ApplicationInfo runtimePack
external0,
"../legacy/Android/data/" + context.getPackageName () + "/files/.__override__").getAbsolutePath ();
+ System.loadLibrary("monodroid");
+
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..5f6eca05fe9
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Resources/MultiDexLoader.Debug.java
@@ -0,0 +1,153 @@
+//
+// 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 {
+
+ static final int KITKAT = 19;
+
+ @Override
+ public boolean onCreate ()
+ {
+ return true;
+ }
+
+ @Override
+ public void attachInfo (android.content.Context context, android.content.pm.ProviderInfo info)
+ {
+ String incrementalDeploymentDir = MonkeyPatcher.getIncrementalDeploymentDir (context);
+
+ File codeCacheDir = context.getCacheDir ();
+ String nativeLibDir = context.getApplicationInfo ().nativeLibraryDir;
+ String dataDir = context.getApplicationInfo ().dataDir;
+ String packageName = context.getPackageName ();
+
+ List dexes = getDexList (packageName, incrementalDeploymentDir);
+ if (dexes != null && dexes.size () > 0) {
+ IncrementalClassLoader.inject (
+ MultiDexLoader.class.getClassLoader (),
+ packageName,
+ codeCacheDir,
+ nativeLibDir,
+ dexes);
+ }
+ super.attachInfo (context, info);
+
+ }
+
+ private List getDexList (String packageName, String incrementalDeploymentDir)
+ {
+ List result = new ArrayList ();
+ String dexDirectory = incrementalDeploymentDir + ".__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.");
+ }
+}
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/Resources/ResourcePatcher.java b/src/Xamarin.Android.Build.Tasks/Resources/ResourcePatcher.java
new file mode 100644
index 00000000000..c08ab452477
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Resources/ResourcePatcher.java
@@ -0,0 +1,177 @@
+//
+// This is a modified version of Bazel source code. Its copyright lines follow below.
+//
+
+// Copyright 2018 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 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 ResourcePatcher extends ContentProvider {
+
+ static final int KITKAT = 19;
+
+ @Override
+ public boolean onCreate ()
+ {
+ return true;
+ }
+
+ @Override
+ public void attachInfo (android.content.Context context, android.content.pm.ProviderInfo info) {
+ String externalResourceFile = getExternalResourceFile (context);
+ super.attachInfo (context, info);
+ MonkeyPatcher.monkeyPatchApplication (context, null, null, externalResourceFile);
+ MonkeyPatcher.monkeyPatchExistingResources (context, externalResourceFile, getActivities (context, false));
+ }
+
+ // ---
+ @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.");
+ }
+
+ private String getExternalResourceFile (android.content.Context context) {
+ String base = MonkeyPatcher.getIncrementalDeploymentDir (context);
+ 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 ("ResourcePatcher", "Cannot find external resources, not patching them in");
+ return null;
+ }
+ }
+ }
+
+ Log.v ("ResourcePatcher", "Found external resources at " + resourceFile);
+ return resourceFile;
+ }
+
+ 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 >= 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 ("ResourcePatcher", Log.WARN)) {
+ Log.w ("ResourcePatcher", "Error retrieving activities", e);
+ }
+ }
+ return list;
+ }
+}
\ No newline at end of file
diff --git a/src/Xamarin.Android.Build.Tasks/Resources/StubApplication.java b/src/Xamarin.Android.Build.Tasks/Resources/StubApplication.java
deleted file mode 100644
index 80f648122fc..00000000000
--- a/src/Xamarin.Android.Build.Tasks/Resources/StubApplication.java
+++ /dev/null
@@ -1,460 +0,0 @@
-//
-// 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.incrementaldeployment;
-
-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;
-
-/**
- * A stub application that patches the class loader, then replaces itself with the real application
- * by applying a liberal amount of reflection on Android internals.
- *
- *
This is, of course, terribly error-prone. Most of this code was tested with API versions
- * 8, 10, 14, 15, 16, 17, 18, 19 and 21 on the Android emulator, a Nexus 5 running Lollipop LRX22C
- * and a Samsung GT-I5800 running Froyo XWJPE. The exception is {@code monkeyPatchAssetManagers},
- * which only works on Kitkat and Lollipop.
- *
- *
Note that due to a bug in Dalvik, this only works on Kitkat if ART is the Java runtime.
- *
- *
Unfortunately, if this does not work, we don't have a fallback mechanism: as soon as we
- * build the APK with this class as the Application, we are committed to going through with it.
- *
- *
This class should use as few other classes as possible before the class loader is patched
- * because any class loaded before it cannot be incrementally deployed.
- */
-public class StubApplication extends Application {
-
- public static boolean real_application_not_instantiated = true;
-
- private static 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 static final FilenameFilter SO = new FilenameFilter() {
- public boolean accept(File dir, String name) {
- return name.endsWith(".so");
- }
- };
-
- private final String realClassName;
- private final String packageName;
-
- private String externalResourceFile;
- private Application realApplication;
-
- public StubApplication() {
- String[] stubApplicationData = getResourceAsString("stub_application_data.txt").split("\n");
- realClassName = stubApplicationData[0];
- packageName = stubApplicationData[1];
-
- Log.v("StubApplication", String.format(
- "StubApplication created. Android package is %s, real application class is %s.",
- packageName, realClassName));
- }
-
- private String getExternalResourceFile() {
- String base = mIncrementalDeploymentDir;
- String resourceFile = base + ".__override__/packaged_resources";
- if (!(new File(resourceFile).isFile())) {
- resourceFile = base + ".__override__/resources";
- if (!(new File(resourceFile).isDirectory())) {
- Log.v("StubApplication", "Cannot find external resources, not patching them in");
- return null;
- }
- }
-
- Log.v("StubApplication", "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) {
- real_application_not_instantiated = true;
- return null;
- } else {
- for (File dex : dexes) {
- if (dex.getName().endsWith(".dex")) {
- result.add(dex.getPath());
- }
- }
- }
-
- return result;
- }
-
- private String getResourceAsString(String resource) {
- InputStream resourceStream = null;
- // try-with-resources would be much nicer, but that requires SDK level 19, and we want this code
- // to be compatible with earlier Android versions
- try {
- resourceStream = getClass().getClassLoader().getResourceAsStream(resource);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int length = 0;
- while ((length = resourceStream.read(buffer)) != -1) {
- baos.write(buffer, 0, length);
- }
-
- String result = new String(baos.toByteArray(), "UTF-8");
- return result;
- } catch (IOException e) {
- throw new IllegalStateException(e);
- } finally {
- if (resourceStream != null) {
- try {
- resourceStream.close();
- } catch (IOException e) {
- // Not much we can do here
- }
- }
- }
- }
-
- @SuppressWarnings("unchecked") // Lots of conversions with generic types
- private void monkeyPatchApplication() {
- // StubApplication 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 StubApplication#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 {
- // Find the ActivityThread instance for the current thread
- Class> activityThread = Class.forName("android.app.ActivityThread");
- Method m = activityThread.getMethod("currentActivityThread");
- m.setAccessible(true);
- Object currentActivityThread = m.invoke(null);
-
- // 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 (initialApplication == StubApplication.this) {
- mInitialApplication.set(currentActivityThread, realApplication);
- }
-
- // Replace all instance of the stub application in ActivityThread#mAllApplications with the
- // real one
- 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) == StubApplication.this) {
- 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) {
- continue;
- }
-
- if (mApplication.get(loadedApk) == StubApplication.this) {
- mApplication.set(loadedApk, realApplication);
- if (externalResourceFile != null) {
- mResDir.set(loadedApk, externalResourceFile);
- }
-
- if (mLoadedApk != null) {
- mLoadedApk.set(realApplication, loadedApk);
- }
- }
- }
- }
- } catch (IllegalAccessException e) {
- throw new IllegalStateException(e);
- } catch (NoSuchFieldException e) {
- throw new IllegalStateException(e);
- } catch (NoSuchMethodException e) {
- throw new IllegalStateException(e);
- } catch (ClassNotFoundException e) {
- throw new IllegalStateException(e);
- } catch (InvocationTargetException e) {
- throw new IllegalStateException(e);
- }
- }
-
- private void monkeyPatchExistingResources() {
- 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 (((int) (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);
-
- // Find the singleton instance of ResourcesManager
- Class> clazz = Class.forName("android.app.ResourcesManager");
- Method mGetInstance = clazz.getDeclaredMethod("getInstance");
- mGetInstance.setAccessible(true);
- Object resourcesManager = mGetInstance.invoke(null);
-
- Field mAssets = null;
- try {
- // Android N and later has mResourcesImpl/*of ResourcesImpl*/.mAssets instead.
- Class> resImplClass = Class.forName("android.content.res.ResourcesImpl");
- mAssets = resImplClass.getDeclaredField("mAssets");
- } catch (NoSuchFieldException ex) {
- try {
- mAssets = Resources.class.getDeclaredField("mAssets");
- mAssets.setAccessible(true);
- } catch (NoSuchFieldException ex2) {
- Log.w("StubApplication", "Failed to replace AssetManager. Assets will fail to load; " + ex.getMessage() + " / " + ex2.getMessage());
- }
- }
- // Iterate over all known Resources objects
- try {
- Field fMActiveResources = clazz.getDeclaredField("mActiveResources");
- fMActiveResources.setAccessible(true);
- @SuppressWarnings("unchecked")
- Map, WeakReference> arrayMap =
- (Map, WeakReference>) fMActiveResources.get(resourcesManager);
- for (WeakReference wr : arrayMap.values()) {
- Resources resources = wr.get();
- // Set the AssetManager of the Resources instance to our brand new one
- if (mAssets != null)
- mAssets.set(resources, newAssetManager);
- resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics());
- }
- } catch (NoSuchFieldException ex) {
- Log.w("StubApplication", "Failed to replace ResourceManager. Some resources may fail to load; " + ex.getMessage());
- }
- } catch (IllegalAccessException e) {
- throw new IllegalStateException(e);
- } catch (NoSuchMethodException e) {
- throw new IllegalStateException(e);
- } catch (ClassNotFoundException e) {
- throw new IllegalStateException(e);
- } catch (InvocationTargetException e) {
- throw new IllegalStateException(e);
- } catch (InstantiationException e) {
- throw new IllegalStateException(e);
- }
- }
-
- private void instantiateRealApplication(File codeCacheDir, String appNativeLibraryDir, String dataDir) {
- externalResourceFile = getExternalResourceFile();
-
- String nativeLibDir = appNativeLibraryDir;
-
- List dexes = getDexList(packageName);
- if (dexes == null)
- return; // it is during initial Seppuku.
- IncrementalClassLoader.inject(
- StubApplication.class.getClassLoader(),
- packageName,
- codeCacheDir,
- nativeLibDir,
- dexes);
-
- try {
- @SuppressWarnings("unchecked")
- Class extends Application> realClass =
- (Class extends Application>) Class.forName(realClassName);
- Constructor extends Application> ctor = realClass.getConstructor();
- realApplication = ctor.newInstance();
- real_application_not_instantiated = false;
- } catch (Exception e) {
- throw new IllegalStateException(e);
- }
- }
-
- // This copies native libraries from apk contents to avoid permission issue and it somehow loads,
- // but causes runtime crash later. Right now it has to be disabled.
- private String copyNativeLibs(String appNativeLibraryDir) throws IOException {
- String nativeLibDir = mIncrementalDeploymentDir + ".__override__/lib";
- File dstdir = new File (nativeLibDir);
- if (!dstdir.exists ())
- dstdir.mkdirs ();
- File srcdir = new File (appNativeLibraryDir);
- for (File src : srcdir.listFiles ()) {
- File dst = new File (nativeLibDir + '/' + src.getName ());
- if (!dst.exists ()) {
- copy (src, dst);
- } else {
- Log.i("StubApplication", "Skip copying native library, already exists: " + dst.getPath());
- }
- }
- return nativeLibDir;
- }
-
- private static void copy(File src, File dst) throws IOException {
- Log.v("StubApplication", "Copying " + src + " -> " + dst);
- InputStream in = null;
- OutputStream out = null;
- try {
- in = new FileInputStream(src);
- out = new FileOutputStream(dst);
-
- // Transfer bytes from in to out
- byte[] buf = new byte[1048576];
- int len;
- while ((len = in.read(buf)) > 0) {
- out.write(buf, 0, len);
- }
- } finally {
- if (in != null) {
- in.close();
- }
-
- if (out != null) {
- out.close();
- }
- }
- }
-
- @Override
- protected void attachBaseContext(Context context) {
-
- mIncrementalDeploymentDir = getIncrementalDeploymentDir(context) + '/';
-
- instantiateRealApplication(
- context.getCacheDir(),
- context.getApplicationInfo().nativeLibraryDir,
- context.getApplicationInfo().dataDir);
-
- // This is called from ActivityThread#handleBindApplication() -> LoadedApk#makeApplication().
- // Application#mApplication is changed right after this call, so we cannot do the monkey
- // patching here. So just forward this method to the real Application instance.
- super.attachBaseContext(context);
-
- if (real_application_not_instantiated) // it is in the bootstrap process with Seppuku.
- return;
-
- try {
- Method attachBaseContext =
- ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
- attachBaseContext.setAccessible(true);
- attachBaseContext.invoke(realApplication, context);
-
- } catch (Exception e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public void onCreate() {
- if (real_application_not_instantiated) // it is in the bootstrap process with Seppuku.
- return;
- monkeyPatchApplication();
- monkeyPatchExistingResources();
- super.onCreate();
- realApplication.onCreate();
- }
-}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
index 377b5d0bb90..bf42e04697a 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
@@ -83,7 +83,6 @@ public class BuildApk : Task
public bool EnableSGenConcurrent { get; set; }
- public string StubApplicationDataFile { get; set; }
public string AndroidEmbedProfilers { get; set; }
public string HttpClientHandlerType { get; set; }
public string TlsProvider { get; set; }
@@ -189,8 +188,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/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 972fb3c3a4f..b50aaf48064 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
@@ -789,6 +789,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 });
@@ -796,7 +807,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 ();
}
@@ -2899,6 +2913,7 @@ public void BuildAMassiveApp()
Verbosity = LoggerVerbosity.Diagnostic,
};
var app1 = new XamarinAndroidApplicationProject() {
+ TargetFrameworkVersion = sb.LatestTargetFrameworkVersion (),
ProjectName = "App1",
AotAssemblies = true,
IsRelease = true,
@@ -2939,6 +2954,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..e4888ca85c7 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 {
@@ -353,8 +354,14 @@ public IList Merge (List subclasses, List select
manifest.SetAttributeValue ("package", PackageName);
+ if (MultiDex)
+ app.Add (CreateMonoRuntimeProvider ("mono.android.MultiDexLoader", null, initOrder: --AppInitOrder));
+
var providerNames = AddMonoRuntimeProviders (app);
+ if (Debug && !embed)
+ app.Add (CreateMonoRuntimeProvider ("mono.android.ResourcePatcher", null, initOrder: --AppInitOrder));
+
if (Debug) {
app.Add (new XComment ("suppress ExportedReceiver"));
app.Add (new XElement ("receiver",
@@ -583,7 +590,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 +616,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 +624,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 92d425292bf..989eed5a633 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
@@ -249,6 +249,7 @@
+
pdb2mdb\BitAccess.cs
@@ -669,8 +670,17 @@
Placeholder.java
-
- StubApplication.java
+
+ MultiDexLoader.java
+
+
+ MultiDexLoader.Debug.java
+
+
+ MonkeyPatcher.java
+
+
+ ResourcePatcher.java
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
index ed1b45c6e54..2fdb25e4667 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.
+
@@ -1908,6 +1909,14 @@ because xbuild doesn't support framework reference assemblies.
ResourceName="Seppuku.java"
OutputPath="$(MonoAndroidIntermediate)android\src\mono\android\Seppuku.java"
Condition="'$(AndroidIncludeDebugSymbols)' == 'True'" />
+
+
@@ -2395,6 +2405,7 @@ because xbuild doesn't support framework reference assemblies.
_GeneratePackageManagerJava;
_FindJavaStubFiles;
_AddStaticResources;
+ $(_AfterAddStaticResources);
_GetMonoPlatformJarPath;
$(_OnResolveMonoAndroidSdks);
_GetLibraryImports;
@@ -2475,7 +2486,7 @@ because xbuild doesn't support framework reference assemblies.
@@ -2593,7 +2604,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)" />
@@ -2656,9 +2666,9 @@ because xbuild doesn't support framework reference assemblies.
_CopyMdbFiles;
_LinkAssemblies;
_GetAddOnPlatformLibraries;
+ _CompileJava;
_CompileDex;
$(_AfterCompileDex);
- _CompileJava;
_CreateBaseApk;
_PrepareAssemblies;
_ResolveSatellitePaths;
@@ -3069,6 +3079,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 0394618ef9b..7eedab4a220 100644
--- a/src/monodroid/jni/monodroid-glue.c
+++ b/src/monodroid/jni/monodroid-glue.c
@@ -604,32 +604,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"
@@ -653,6 +627,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 ()
{
@@ -663,8 +677,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)
@@ -2640,39 +2653,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
@@ -2686,6 +2709,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
@@ -3951,6 +3979,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;
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)$