From 5b5f668ae9b7ed46041fc9e72e1abf2fd2a20e1c Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Mon, 8 May 2017 10:15:15 +0100 Subject: [PATCH 1/2] [Xamarim.Android.Build.Tasks] Add support for new InstantRun This commit does a couple of things. 1) It reworks the monodroid-glue to copy any .so file it finds in the .__override__ directory over to the local app directory. This should allow us to fast deploy native libraries. Which means we no longer need to re-build the apk if a native library changes. 2) Replaces the StubApplication with a new MultiDexLoader ContentProvider. This provider is reposible for doing all the things the previous StubApplication used to do. But because it is not derived from an Application it means we can now support custom Application types for our debug builds. --- build-tools/scripts/RunTests.targets | 1 + build-tools/scripts/TestApks.targets | 1 + .../Test/Mono.Android-Test.Shared.projitems | 45 ++ .../Test/Mono.Android-Test.Shared.shproj | 11 + .../Test/Mono.Android-Tests.csproj | 1 + src/Xamarin.Android.Build.Tasks.tpnitems | 3 +- .../Resources/MonkeyPatcher.java | 520 ++++++++++++++++++ .../Resources/MonoPackageManager.java | 15 +- .../Resources/MultiDexLoader.Debug.java | 153 ++++++ .../Resources/MultiDexLoader.java | 126 +++++ .../Resources/ResourcePatcher.java | 177 ++++++ .../Resources/StubApplication.java | 460 ---------------- .../Tasks/BuildApk.cs | 4 +- .../Tasks/ComputeHash.cs | 46 ++ .../Tasks/CreateMultiDexMainDexClassList.cs | 18 +- .../Tasks/GenerateJavaStubs.cs | 3 + .../Xamarin.Android.Build.Tests/BuildTest.cs | 18 +- .../Utilities/ManifestDocument.cs | 17 +- .../Xamarin.Android.Build.Tasks.csproj | 14 +- .../Xamarin.Android.Common.targets | 19 +- src/monodroid/jni/monodroid-glue.c | 127 +++-- tests/RunApkTests.targets | 1 + .../Mono.Android-TestsMultiDex.csproj | 102 ++++ .../Mono.Android-TestsMultiDex.projitems | 15 + .../Mono.Android-TestsMultiDex.targets | 19 + .../Properties/AndroidManifest.xml | 12 + .../Resources/AboutResources.txt | 44 ++ .../Resources/Resource.designer.cs | 270 +++++++++ .../Resources/color/WhiterShadeOfPale.xml | 5 + .../Resources/drawable/AndroidPressed.png | Bin 0 -> 2337 bytes .../Resources/drawable/Icon.png | Bin 0 -> 2574 bytes .../Resources/drawable/android_button.xml | 8 + .../Resources/drawable/android_focused.png | Bin 0 -> 2785 bytes .../Resources/drawable/android_normal.png | Bin 0 -> 2056 bytes .../Resources/layout/FragmentFixup.axml | 32 ++ .../Resources/layout/Main.axml | 54 ++ .../Resources/layout/NameFixups.axml | 49 ++ .../Resources/values/Strings.xml | 5 + .../Resources/values/colors.xml | 4 + .../Resources/values/theme.axml | 4 + .../Resources/values/theme.xml | 4 + .../Resources/xml/XmlReaderResourceParser.xml | 1 + .../apk-sizes-definitions.txt | 6 + 43 files changed, 1881 insertions(+), 533 deletions(-) create mode 100644 src/Mono.Android/Test/Mono.Android-Test.Shared.projitems create mode 100644 src/Mono.Android/Test/Mono.Android-Test.Shared.shproj create mode 100644 src/Xamarin.Android.Build.Tasks/Resources/MonkeyPatcher.java create mode 100644 src/Xamarin.Android.Build.Tasks/Resources/MultiDexLoader.Debug.java create mode 100644 src/Xamarin.Android.Build.Tasks/Resources/MultiDexLoader.java create mode 100644 src/Xamarin.Android.Build.Tasks/Resources/ResourcePatcher.java delete mode 100644 src/Xamarin.Android.Build.Tasks/Resources/StubApplication.java create mode 100644 src/Xamarin.Android.Build.Tasks/Tasks/ComputeHash.cs create mode 100644 tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj create mode 100644 tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.projitems create mode 100644 tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.targets create mode 100644 tests/Runtime-MultiDex/Properties/AndroidManifest.xml create mode 100644 tests/Runtime-MultiDex/Resources/AboutResources.txt create mode 100644 tests/Runtime-MultiDex/Resources/Resource.designer.cs create mode 100644 tests/Runtime-MultiDex/Resources/color/WhiterShadeOfPale.xml create mode 100644 tests/Runtime-MultiDex/Resources/drawable/AndroidPressed.png create mode 100644 tests/Runtime-MultiDex/Resources/drawable/Icon.png create mode 100644 tests/Runtime-MultiDex/Resources/drawable/android_button.xml create mode 100644 tests/Runtime-MultiDex/Resources/drawable/android_focused.png create mode 100644 tests/Runtime-MultiDex/Resources/drawable/android_normal.png create mode 100644 tests/Runtime-MultiDex/Resources/layout/FragmentFixup.axml create mode 100644 tests/Runtime-MultiDex/Resources/layout/Main.axml create mode 100644 tests/Runtime-MultiDex/Resources/layout/NameFixups.axml create mode 100644 tests/Runtime-MultiDex/Resources/values/Strings.xml create mode 100644 tests/Runtime-MultiDex/Resources/values/colors.xml create mode 100644 tests/Runtime-MultiDex/Resources/values/theme.axml create mode 100644 tests/Runtime-MultiDex/Resources/values/theme.xml create mode 100644 tests/Runtime-MultiDex/Resources/xml/XmlReaderResourceParser.xml create mode 100644 tests/Runtime-MultiDex/apk-sizes-definitions.txt 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.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> 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.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..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..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> arrayMap = - (Map>) 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 realClass = - (Class) Class.forName(realClassName); - Constructor 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 db16612819a..a66b2ee496d 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -250,6 +250,7 @@ + pdb2mdb\BitAccess.cs @@ -670,8 +671,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 865e3853829..fb784d67e15 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 0000000000000000000000000000000000000000..fe81ff9e279c00aedb46b20018de030caea50cab GIT binary patch literal 2337 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 + + + + + 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 0000000000000000000000000000000000000000..f84d0fe4a5e403782c0d4db032a8b2f3053c80b8 GIT binary patch literal 2785 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 m>h&96Gax~ICRL&Fq2e#q;;tApN9c + + + + + + 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)$ From 86d9fb509c4d43e8185c75b5c65c6dda04b0dda1 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Tue, 25 Sep 2018 19:16:02 +0100 Subject: [PATCH 2/2] Removed Logging --- .../Resources/MonoPackageManager.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Resources/MonoPackageManager.java b/src/Xamarin.Android.Build.Tasks/Resources/MonoPackageManager.java index 1d956fa4e84..8a1381044ee 100644 --- a/src/Xamarin.Android.Build.Tasks/Resources/MonoPackageManager.java +++ b/src/Xamarin.Android.Build.Tasks/Resources/MonoPackageManager.java @@ -22,7 +22,6 @@ 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; @@ -47,16 +46,8 @@ 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,