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