From 130905e1612f1c3f41b6b233056cae2753270630 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 20 Dec 2019 12:57:19 -0500 Subject: [PATCH] [Mono.Android-Tests] Run Java.Interop tests as well. (#3393) Context: xamarin/monodroid@e318861 Context: 7d32ef3 Context: 1a2eb95 When use of Java.Interop.dll was originally added in xamarin/monodroid@e318861, it didn't implement the "full" Java.Interop.JniRuntime abstraction, which meant that until commit 1a2eb95, trying to instantiate a JavaInt32Array instance would result in a NotImplementedException, because of Android.Runtime.AndroidValueManager.AddPeer(). Commit 1a2eb95 removed the exception but didn't implement JniRuntime.JniValueManager.AddPeer(), meaning even though a JavaInt32Array could be instantiated, it wouldn't work "properly", e.g. JniRuntime.JniValueManager.PeekPeer() wouldn't find the JavaInt32Array instance when given the same handle. This in turn "happened" because Mono.Android-Tests.dll never executed the unit tests for Java.Interop.dll, which it couldn't do, because it didn't fully support the semantics. Add the Java.Interop.dll unit tests to Mono.Android-Tests.dll, and fix Mono.Android.dll and company so that they can actually pass: Add a new Java.Interop-Tests.csproj project to "host" the Java.Interop unit tests in a MonoAndroid-profile project. Add java-interop.jar to the javac command line generated by Jar.targets, so that e.g. com.xamarin.java_interop.ManagedPeer can be used. Update Mono.Android.dll so that the Java.Interop unit tests work. This includes "migrating" the (jobject => instance) mapping previously accessible via Java.Lang.Object.GetObject() into Android.Runtime.AndroidValueManager. AndroidRuntime.GetExceptionForThrowable() will now "unwrap" a Java.Interop.JavaProxyThrowable instance. AndroidTypeManager.GetSimpleReferences() can't use JNIEnv.GetJniName() on-device because that always returns java/lang/Object, even if a type can't be found, which breaks several Java.Interop unit tests. Use JNIEnv.monodroid_typemap_managed_to_java() instead. AndroidTypeManager.RegisterNativeMembers() will now invoke JniRuntime.JniTypeManager.RegisterNativeMembers() when methods is empty, so that classes can handle their own registration. This was needed so that Java.InteropTests.CallVirtualFromConstructorDerived could actually be properly instantiated and participate in tests. Java.Interop.TypeManager.CreateProxy() now supports Java.Interop-style (ref JniObjectReference, JniObjectReferenceOptions) constructors. Java.Interop.JavaObject and Java.Interop.JavaException are now bridgeable types, and can participate in GC's. This means that e.g. JavaInt32Array instances won't be prematurely collected. src/monodroid will also verify that bridgeable types contain the fields required for use, and provide a better error message when any fields are missing. The Java.Lang.Object and Java.Lang.Throwable finalizers must check Environment.HasShutdownStarted before using JniEnvironment.Runtime, as finalization order is unspecified, so the JniRuntime instance may have been finalized first. With the new changes, the size of Xamarin.Forms_Performance_Integration-Signed.apk increases by ~4kb. I consider this negligible. App startup time isn't significantly impacted either: the average of five Xamarin.Forms_Performance_Integration app launches is 706.2ms with these changes, vs. 706.8ms without these changes. Other Build System Changes: Set $(AndroidLinkMode)=r8 in various projects for consistency with 4bb4b2e. Make Mono.Android-Tests debuggable in Release config, so that GREF logs/etc. can be easily extracted from device, if necessary. Split up external/Java.Interop checkout & preparation logic so that it is usable within unit test environments, which don't contain a full build tree. Add some build-time test-related artifacts to @(_BuildStatusFiles) so that build-time log files are kept. TODO: Java.Lang.Object should be updated to inherit from Java.Interop.JavaObject Java.Lang.Throwable should be updated to inherit from Java.Interop.JavaException generator needs to be updated to begin avoiding the JNIEnv methods, so that non-Java.Lang.*-inheriting types can be used. --- Xamarin.Android-Tests.sln | 43 ++- .../result-packaging.targets | 8 + build-tools/scripts/Jar.targets | 19 +- .../Scenarios/Scenario_PrepareExternal.cs | 18 + .../xaprepare/Scenarios/Scenario_Required.cs | 1 + .../xaprepare/Scenarios/Scenario_Standard.cs | 1 + .../Steps/Step_PrepareExternal.Unix.cs | 25 -- .../Step_PrepareExternalJavaInterop.Unix.cs | 42 ++ ...Step_PrepareExternalJavaInterop.Windows.cs | 14 + .../Steps/Step_PrepareExternalJavaInterop.cs | 18 + .../ThirdPartyNotices/Java.Interop.cs | 8 +- .../xaprepare/xaprepare/xaprepare.csproj | 4 + .../Android.Runtime/AndroidRuntime.cs | 359 +++++++++++++++++- .../Android.Runtime/InputStreamInvoker.cs | 4 +- src/Mono.Android/Android.Runtime/JNIEnv.cs | 3 + .../Android.Runtime/JavaCollection.cs | 4 +- .../Android.Runtime/JavaDictionary.cs | 4 +- src/Mono.Android/Android.Runtime/JavaList.cs | 4 +- src/Mono.Android/Android.Runtime/JavaSet.cs | 4 +- .../Android.Runtime/OutputStreamInvoker.cs | 4 +- .../Android.Runtime/XmlPullParserReader.cs | 8 +- src/Mono.Android/Java.Interop/JavaConvert.cs | 4 +- .../Java.Interop/JavaObjectExtensions.cs | 2 +- src/Mono.Android/Java.Interop/Runtime.cs | 9 +- src/Mono.Android/Java.Interop/TypeManager.cs | 38 +- src/Mono.Android/Java.Lang/Object.cs | 237 +----------- src/Mono.Android/Java.Lang/Throwable.cs | 26 +- .../Test/Java.Interop-Tests/.gitignore | 1 + .../Java.Interop-Tests.csproj | 90 +++++ .../Java.Interop-Tests.targets | 24 ++ .../JavaInterop_Tests_Reference.cs | 9 + .../Properties/AssemblyInfo.cs | 30 ++ .../Resources/AboutResources.txt | 44 +++ .../Test/Java.Interop-Tests/packages.config | 4 + .../Test/Mono.Android-Tests.csproj | 7 + .../Test/Properties/AndroidManifest.xml | 2 +- .../NUnitInstrumentation.cs | 5 +- .../TestInstrumentation.cs | 1 + src/Mono.Android/Test/proguard.cfg | 3 + .../Linker/PreserveLists/Java.Interop.xml | 17 + .../Linker/PreserveLists/Mono.Android.xml | 12 + .../Resources/proguard_xamarin.cfg | 1 + .../Xamarin.Android.Build.Tasks.csproj | 3 + .../Xamarin.Android.Common.props.in | 1 + src/monodroid/jni/monodroid-glue.cc | 21 +- src/monodroid/jni/osbridge.cc | 12 +- src/monodroid/jni/osbridge.hh | 16 +- .../Mono.Android-TestsAppBundle.csproj | 8 + .../Mono.Android-TestsMultiDex.csproj | 8 + tests/TestRunner.Core/TestRunner.Core.csproj | 5 +- .../TestRunner.NUnit/TestRunner.NUnit.csproj | 5 +- 51 files changed, 894 insertions(+), 346 deletions(-) create mode 100644 build-tools/xaprepare/xaprepare/Scenarios/Scenario_PrepareExternal.cs create mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_PrepareExternalJavaInterop.Unix.cs create mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_PrepareExternalJavaInterop.Windows.cs create mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_PrepareExternalJavaInterop.cs create mode 100644 src/Mono.Android/Test/Java.Interop-Tests/.gitignore create mode 100644 src/Mono.Android/Test/Java.Interop-Tests/Java.Interop-Tests.csproj create mode 100644 src/Mono.Android/Test/Java.Interop-Tests/Java.Interop-Tests.targets create mode 100644 src/Mono.Android/Test/Java.Interop-Tests/Java.InteropTests/JavaInterop_Tests_Reference.cs create mode 100644 src/Mono.Android/Test/Java.Interop-Tests/Properties/AssemblyInfo.cs create mode 100644 src/Mono.Android/Test/Java.Interop-Tests/Resources/AboutResources.txt create mode 100644 src/Mono.Android/Test/Java.Interop-Tests/packages.config create mode 100644 src/Mono.Android/Test/proguard.cfg create mode 100644 src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Java.Interop.xml diff --git a/Xamarin.Android-Tests.sln b/Xamarin.Android-Tests.sln index 0d6de66b5dc..5b2db998e1b 100644 --- a/Xamarin.Android-Tests.sln +++ b/Xamarin.Android-Tests.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27428.2037 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29411.108 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Android-Tests", "src\Mono.Android\Test\Mono.Android-Tests.csproj", "{40EAD437-216B-4DF4-8258-3F47E1672C3A}" EndProject @@ -90,6 +90,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Android-Test.Library", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Android-TestsMultiDex", "tests\Runtime-MultiDex\Mono.Android-TestsMultiDex.csproj", "{9ECBEA14-B79F-4F92-9266-495C03A32571}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Java.Interop.GenericMarshaler", "external\Java.Interop\src\Java.Interop.GenericMarshaler\Java.Interop.GenericMarshaler.csproj", "{D1243BAB-23CA-4566-A2A3-3ADA2C2DC3AF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop-Tests", "src\Mono.Android\Test\Java.Interop-Tests\Java.Interop-Tests.csproj", "{6CB00820-A66B-43E5-8785-ED456C6E9F39}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Java.Interop", "external\Java.Interop\src\Java.Interop\Java.Interop.csproj", "{94BD81F7-B06F-4295-9636-F8A3B6BDC762}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Mono.Android\Test\Mono.Android-Test.Shared.projitems*{0ab4956e-6fb9-4da0-9d49-ab65a3ff403a}*SharedItemsImports = 13 @@ -111,6 +117,10 @@ Global {2305B00D-DE81-4744-B0DA-357835CAFE5A}.Debug|Any CPU.Build.0 = Debug|Any CPU {2305B00D-DE81-4744-B0DA-357835CAFE5A}.Release|Any CPU.ActiveCfg = Release|Any CPU {2305B00D-DE81-4744-B0DA-357835CAFE5A}.Release|Any CPU.Build.0 = Release|Any CPU + {6BE66B30-9346-4DA6-B09A-0CDC1DFE33C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6BE66B30-9346-4DA6-B09A-0CDC1DFE33C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BE66B30-9346-4DA6-B09A-0CDC1DFE33C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6BE66B30-9346-4DA6-B09A-0CDC1DFE33C2}.Release|Any CPU.Build.0 = Release|Any CPU {05768F39-7BAF-43E6-971E-712F5771E88E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {05768F39-7BAF-43E6-971E-712F5771E88E}.Debug|Any CPU.Build.0 = Debug|Any CPU {05768F39-7BAF-43E6-971E-712F5771E88E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -223,18 +233,6 @@ Global {056ED976-618F-4A3E-910E-AA25230C2296}.Debug|Any CPU.Build.0 = Debug|Any CPU {056ED976-618F-4A3E-910E-AA25230C2296}.Release|Any CPU.ActiveCfg = Release|Any CPU {056ED976-618F-4A3E-910E-AA25230C2296}.Release|Any CPU.Build.0 = Release|Any CPU - {B160F0E7-799A-4EB9-92B8-D71623C7674A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B160F0E7-799A-4EB9-92B8-D71623C7674A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B160F0E7-799A-4EB9-92B8-D71623C7674A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B160F0E7-799A-4EB9-92B8-D71623C7674A}.Release|Any CPU.Build.0 = Release|Any CPU - {FA8EEC88-CA3C-4D69-B206-54B392570DC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FA8EEC88-CA3C-4D69-B206-54B392570DC6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FA8EEC88-CA3C-4D69-B206-54B392570DC6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FA8EEC88-CA3C-4D69-B206-54B392570DC6}.Release|Any CPU.Build.0 = Release|Any CPU - {6BE66B30-9346-4DA6-B09A-0CDC1DFE33C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6BE66B30-9346-4DA6-B09A-0CDC1DFE33C2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6BE66B30-9346-4DA6-B09A-0CDC1DFE33C2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6BE66B30-9346-4DA6-B09A-0CDC1DFE33C2}.Release|Any CPU.Build.0 = Release|Any CPU {8B5E63B7-8C18-4BA7-BAAB-A1955B257F5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8B5E63B7-8C18-4BA7-BAAB-A1955B257F5E}.Debug|Any CPU.Build.0 = Debug|Any CPU {8B5E63B7-8C18-4BA7-BAAB-A1955B257F5E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -249,6 +247,18 @@ Global {9ECBEA14-B79F-4F92-9266-495C03A32571}.Release|Any CPU.ActiveCfg = Release|Any CPU {9ECBEA14-B79F-4F92-9266-495C03A32571}.Release|Any CPU.Build.0 = Release|Any CPU {9ECBEA14-B79F-4F92-9266-495C03A32571}.Release|Any CPU.Deploy.0 = Release|Any CPU + {D1243BAB-23CA-4566-A2A3-3ADA2C2DC3AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1243BAB-23CA-4566-A2A3-3ADA2C2DC3AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1243BAB-23CA-4566-A2A3-3ADA2C2DC3AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1243BAB-23CA-4566-A2A3-3ADA2C2DC3AF}.Release|Any CPU.Build.0 = Release|Any CPU + {6CB00820-A66B-43E5-8785-ED456C6E9F39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CB00820-A66B-43E5-8785-ED456C6E9F39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CB00820-A66B-43E5-8785-ED456C6E9F39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CB00820-A66B-43E5-8785-ED456C6E9F39}.Release|Any CPU.Build.0 = Release|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -256,6 +266,7 @@ Global GlobalSection(NestedProjects) = preSolution {40EAD437-216B-4DF4-8258-3F47E1672C3A} = {EFBC4DC0-DBFF-4DAA-B0B8-6D0CB02A25F5} {2305B00D-DE81-4744-B0DA-357835CAFE5A} = {43A4FB09-279A-4138-8027-EC1E1CED2E8A} + {6BE66B30-9346-4DA6-B09A-0CDC1DFE33C2} = {43A4FB09-279A-4138-8027-EC1E1CED2E8A} {05768F39-7BAF-43E6-971E-712F5771E88E} = {D6BFEDF6-2F48-44B2-9553-F2F6F92531BD} {9D5C83B5-70D5-4CC2-9DB7-78B23DC8F255} = {D6BFEDF6-2F48-44B2-9553-F2F6F92531BD} {EF798EB3-D639-4E09-9DB0-233E67F727B0} = {2EFFECF5-1CCA-4005-AE62-1D6F01C88DF4} @@ -280,10 +291,12 @@ Global {F4DAFD78-BE76-46C9-A1AD-85D8C91CD77B} = {9B63992C-2201-4BB0-BD00-D637B481A995} {2DD1EE75-6D8D-4653-A800-0A24367F7F38} = {9B63992C-2201-4BB0-BD00-D637B481A995} {37CAA28C-40BE-4253-BA68-CC5D7316A617} = {68B8E272-5B12-47AA-8923-550B9CE535C7} - {6BE66B30-9346-4DA6-B09A-0CDC1DFE33C2} = {43A4FB09-279A-4138-8027-EC1E1CED2E8A} {0AB4956E-6FB9-4DA0-9D49-AB65A3FF403A} = {EFBC4DC0-DBFF-4DAA-B0B8-6D0CB02A25F5} {8CB5FF58-FF95-43B9-9064-9ACE9525866F} = {EFBC4DC0-DBFF-4DAA-B0B8-6D0CB02A25F5} {9ECBEA14-B79F-4F92-9266-495C03A32571} = {EFBC4DC0-DBFF-4DAA-B0B8-6D0CB02A25F5} + {D1243BAB-23CA-4566-A2A3-3ADA2C2DC3AF} = {EFBC4DC0-DBFF-4DAA-B0B8-6D0CB02A25F5} + {6CB00820-A66B-43E5-8785-ED456C6E9F39} = {EFBC4DC0-DBFF-4DAA-B0B8-6D0CB02A25F5} + {94BD81F7-B06F-4295-9636-F8A3B6BDC762} = {EFBC4DC0-DBFF-4DAA-B0B8-6D0CB02A25F5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8643CD20-B195-4919-8135-27549488237E} diff --git a/build-tools/Xamarin.Android.Tools.BootstrapTasks/result-packaging.targets b/build-tools/Xamarin.Android.Tools.BootstrapTasks/result-packaging.targets index 4a555505ced..52daf68165d 100644 --- a/build-tools/Xamarin.Android.Tools.BootstrapTasks/result-packaging.targets +++ b/build-tools/Xamarin.Android.Tools.BootstrapTasks/result-packaging.targets @@ -12,6 +12,14 @@ <_BuildStatusFiles Include="$(XamarinAndroidSourcePath)bin\Build$(Configuration)\*.projitems" /> <_BuildStatusFiles Include="$(XamarinAndroidSourcePath)bin\Build$(Configuration)\*.cmake" /> <_BuildStatusFiles Include="$(XamarinAndroidSourcePath)bin\Build$(Configuration)\*.targets" /> + <_BuildStatusFiles Include="$(XamarinAndroidSourcePath)bin\Test$(Configuration)\XABuildConfig.cs" Condition="Exists('$(XamarinAndroidSourcePath)bin\Test$(Configuration)\XABuildConfig.cs')" /> + <_BuildStatusFiles Include="$(XamarinAndroidSourcePath)bin\Test$(Configuration)\*.binlog" /> + <_BuildStatusFiles Include="$(XamarinAndroidSourcePath)bin\Test$(Configuration)\prepare*.log" /> + <_BuildStatusFiles Include="$(XamarinAndroidSourcePath)bin\Test$(Configuration)\*.mk" /> + <_BuildStatusFiles Include="$(XamarinAndroidSourcePath)bin\Test$(Configuration)\*.projitems" /> + <_BuildStatusFiles Include="$(XamarinAndroidSourcePath)bin\Test$(Configuration)\*.cmake" /> + <_BuildStatusFiles Include="$(XamarinAndroidSourcePath)bin\Test$(Configuration)\*.targets" /> + <_BuildStatusFiles Include="$(XamarinAndroidSourcePath)external\Java.Interop\bin\Build$(Configuration)\*.props" /> <_BuildStatusFiles Include="$(XamarinAndroidSourcePath)**\ThirdPartyNotices.txt" /> <_BuildStatusFiles Include="$(XamarinAndroidSourcePath)**\config.log" /> <_BuildStatusFiles Include="$(XamarinAndroidSourcePath)**\config.status" /> diff --git a/build-tools/scripts/Jar.targets b/build-tools/scripts/Jar.targets index 327013349b9..91552910d10 100644 --- a/build-tools/scripts/Jar.targets +++ b/build-tools/scripts/Jar.targets @@ -11,8 +11,20 @@ $(JavaSdkDirectory)\bin\javac + + + <_JIJar_InTree>$([System.IO.Path]::GetFullPath ('$(XAInstallPrefix)'))\xbuild\Xamarin\Android\java-interop.jar + <_JIJar_System Condition=" '$(_XamarinAndroidMSBuildDirectory)' != '' ">$(_XamarinAndroidMSBuildDirectory)\java-interop.jar + <_JavaInteropJarPath Condition=" Exists($(_JIJar_InTree)) ">$(_JIJar_InTree) + <_JavaInteropJarPath Condition=" '$(_JavaInteropJarPath)' == '' ">$(_JIJar_System) + + + @@ -25,10 +37,11 @@ <_Jar>"$(JarPath)" <_Targets>-source $(_JavacSourceVersion) -target $(_JavacTargetVersion) <_DestDir>$(IntermediateOutputPath)__CreateTestJarFile-bin - <_AndroidJar>-cp "$(AndroidSdkDirectory)\platforms\android-$(_AndroidApiLevelName)\android.jar" + <_AndroidJar>-bootclasspath "$(AndroidSdkDirectory)\platforms\android-$(_AndroidApiLevelName)\android.jar" + <_CP>-cp "$(_JavaInteropJarPath)" - + ExecuteOSSpecific (Context context, NuGetRunner nuget) if (!result) return false; - string javaInteropDir = context.Properties.GetRequiredValue (KnownProperties.JavaInteropFullPath); - Log.StatusLine (); - result = await make.Run ( - logTag: "java-interop-prepare", - workingDirectory: javaInteropDir, - arguments: new List { - "prepare", - $"CONFIGURATION={context.Configuration}", - $"JAVA_HOME={context.OS.JavaHome}", - $"JI_MAX_JDK={Configurables.Defaults.MaxJDKVersion}", - } - ); - if (!result) - return false; - - Log.StatusLine (); - result = await make.Run ( - logTag: "java-interop-props", - workingDirectory: javaInteropDir, - arguments: new List { - $"bin/Build{context.Configuration}/JdkInfo.props", - $"CONFIGURATION={context.Configuration}", - $"JI_MAX_JDK={Configurables.Defaults.MaxJDKVersion}", - } - ); return result; } } diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_PrepareExternalJavaInterop.Unix.cs b/build-tools/xaprepare/xaprepare/Steps/Step_PrepareExternalJavaInterop.Unix.cs new file mode 100644 index 00000000000..2da742d8e1c --- /dev/null +++ b/build-tools/xaprepare/xaprepare/Steps/Step_PrepareExternalJavaInterop.Unix.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace Xamarin.Android.Prepare +{ + partial class Step_PrepareExternalJavaInterop + { + async Task ExecuteOSSpecific (Context context) + { + string javaInteropDir = context.Properties.GetRequiredValue (KnownProperties.JavaInteropFullPath); + Log.StatusLine (); + var make = new MakeRunner (context) { + NoParallelJobs = true + }; + var result = await make.Run ( + logTag: "java-interop-prepare", + workingDirectory: javaInteropDir, + arguments: new List { + "prepare", + $"CONFIGURATION={context.Configuration}", + $"JAVA_HOME={context.OS.JavaHome}", + $"JI_MAX_JDK={Configurables.Defaults.MaxJDKVersion}", + } + ); + if (!result) + return false; + + Log.StatusLine (); + result = await make.Run ( + logTag: "java-interop-props", + workingDirectory: javaInteropDir, + arguments: new List { + $"bin/Build{context.Configuration}/JdkInfo.props", + $"CONFIGURATION={context.Configuration}", + $"JI_MAX_JDK={Configurables.Defaults.MaxJDKVersion}", + } + ); + return result; + } + } +} diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_PrepareExternalJavaInterop.Windows.cs b/build-tools/xaprepare/xaprepare/Steps/Step_PrepareExternalJavaInterop.Windows.cs new file mode 100644 index 00000000000..e341c97cb89 --- /dev/null +++ b/build-tools/xaprepare/xaprepare/Steps/Step_PrepareExternalJavaInterop.Windows.cs @@ -0,0 +1,14 @@ +using System.IO; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Xamarin.Android.Prepare +{ + partial class Step_PrepareExternalJavaInterop + { + async Task ExecuteOSSpecific (Context context) + { + return true; + } + } +} diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_PrepareExternalJavaInterop.cs b/build-tools/xaprepare/xaprepare/Steps/Step_PrepareExternalJavaInterop.cs new file mode 100644 index 00000000000..e57193610e1 --- /dev/null +++ b/build-tools/xaprepare/xaprepare/Steps/Step_PrepareExternalJavaInterop.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace Xamarin.Android.Prepare +{ + partial class Step_PrepareExternalJavaInterop : Step + { + public Step_PrepareExternalJavaInterop () + : base ("Preparing external/Java.Interop") + {} + + protected override async Task Execute (Context context) + { + return await ExecuteOSSpecific (context); + } + } +} diff --git a/build-tools/xaprepare/xaprepare/ThirdPartyNotices/Java.Interop.cs b/build-tools/xaprepare/xaprepare/ThirdPartyNotices/Java.Interop.cs index b9fa35b9076..ca4bcd82c9e 100644 --- a/build-tools/xaprepare/xaprepare/ThirdPartyNotices/Java.Interop.cs +++ b/build-tools/xaprepare/xaprepare/ThirdPartyNotices/Java.Interop.cs @@ -13,7 +13,7 @@ class JavaInterop_External_Dependencies_Group : ThirdPartyNoticeGroup new JavaInterop_xamarin_Java_Interop_TPN (), new JavaInterop_gityf_crc_TPN (), new JavaInterop_xamarin_mono_cecil_TPN (), - new JavaInterop_jonpryor_mono_linq_expressions_TPN (), + new JavaInterop_jbevain_mono_linq_expressions_TPN (), new JavaInterop_mono_csharp_TPN (), new JavaInterop_mono_LineEditor_TPN (), new JavaInterop_mono_Options_TPN (), @@ -82,12 +82,12 @@ class JavaInterop_xamarin_mono_cecil_TPN : ThirdPartyNotice public override string LicenseText => null; } - class JavaInterop_jonpryor_mono_linq_expressions_TPN : ThirdPartyNotice + class JavaInterop_jbevain_mono_linq_expressions_TPN : ThirdPartyNotice { - static readonly Uri url = new Uri ("https://github.com/jonpryor/mono.linq.expressions/"); + static readonly Uri url = new Uri ("https://github.com/jbevain/mono.linq.expressions/"); public override string LicenseFile => null; - public override string Name => "jonpryor/mono.linq.expressions"; + public override string Name => "jbevain/mono.linq.expressions"; public override Uri SourceUrl => url; public override string LicenseText => @" diff --git a/build-tools/xaprepare/xaprepare/xaprepare.csproj b/build-tools/xaprepare/xaprepare/xaprepare.csproj index 520c68b7e7d..81d59485f1e 100644 --- a/build-tools/xaprepare/xaprepare/xaprepare.csproj +++ b/build-tools/xaprepare/xaprepare/xaprepare.csproj @@ -127,6 +127,7 @@ + @@ -142,6 +143,7 @@ + @@ -196,6 +198,7 @@ + @@ -259,6 +262,7 @@ + diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index f0fc6bd0f22..93a5fc0e9de 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -36,14 +36,23 @@ public override string GetCurrentManagedThreadStackTrace (int skipFrames, bool f .ToString (); } - public override Exception GetExceptionForThrowable (ref JniObjectReference value, JniObjectReferenceOptions transfer) + public override Exception GetExceptionForThrowable (ref JniObjectReference reference, JniObjectReferenceOptions options) { - var throwable = Java.Lang.Object.GetObject(value.Handle, JniHandleOwnership.DoNotTransfer); - JniObjectReference.Dispose (ref value, transfer); - var p = throwable as JavaProxyThrowable; - if (p != null) - return p.InnerException; - return throwable; + if (!reference.IsValid) + return null; + var peeked = JNIEnv.AndroidValueManager.PeekPeer (reference); + var peekedExc = peeked as Exception; + if (peekedExc == null) { + var throwable = Java.Lang.Object.GetObject (reference.Handle, JniHandleOwnership.DoNotTransfer); + JniObjectReference.Dispose (ref reference, options); + return throwable; + } + JniObjectReference.Dispose (ref reference, options); + var unwrapped = JNIEnv.AndroidValueManager.UnboxException (peeked); + if (unwrapped != null) { + return unwrapped; + } + return peekedExc; } public override void RaisePendingException (Exception pendingException) @@ -222,11 +231,16 @@ protected override IEnumerable GetTypesForSimpleReference (string jniSimpl protected override IEnumerable GetSimpleReferences (Type type) { - var j = JNIEnv.GetJniName (type); - if (j == null) - return base.GetSimpleReferences (type); - return base.GetSimpleReferences (type) - .Concat (Enumerable.Repeat (j, 1)); + foreach (var simpleRef in base.GetSimpleReferences (type)) { + yield return simpleRef; + } + var j = JNIEnv.monodroid_typemap_managed_to_java (type.FullName + ", " + type.Assembly.GetName ().Name); + if (j != IntPtr.Zero) { + yield return Marshal.PtrToStringAnsi (j); + } + if (JNIEnv.IsRunningOnDesktop) { + yield return JavaNativeTypeManager.ToJniName (type); + } } delegate Delegate GetCallbackHandler (); @@ -318,17 +332,21 @@ static bool CallRegisterMethodByIndex (JniNativeMethodRegistrationArguments argu } } - public override void RegisterNativeMembers (JniType jniType, Type type, string methods) + public override void RegisterNativeMembers (JniType nativeClass, Type type, string methods) { - if (FastRegisterNativeMembers (jniType, type, methods)) + if (FastRegisterNativeMembers (nativeClass, type, methods)) return; - if (methods == null) + if (string.IsNullOrEmpty (methods)) { + base.RegisterNativeMembers (nativeClass, type, methods); return; + } string[] members = methods.Split ('\n'); - if (members.Length == 0) + if (members.Length < 2) { + base.RegisterNativeMembers (nativeClass, type, methods); return; + } JniNativeMethodRegistration[] natives = new JniNativeMethodRegistration [members.Length-1]; for (int i = 0; i < members.Length; ++i) { @@ -351,41 +369,344 @@ public override void RegisterNativeMembers (JniType jniType, Type type, string m natives [i] = new JniNativeMethodRegistration (toks [0], toks [1], callback); } - JniEnvironment.Types.RegisterNatives (jniType.PeerReference, natives, natives.Length); + JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, natives.Length); } } class AndroidValueManager : JniRuntime.JniValueManager { + Dictionary instances = new Dictionary (); + public override void WaitForGCBridgeProcessing () { JNIEnv.WaitForBridgeProcessing (); } + public override IJavaPeerable CreatePeer (ref JniObjectReference reference, JniObjectReferenceOptions options, Type targetType) + { + if (!reference.IsValid) + return null; + + var peer = Java.Interop.TypeManager.CreateInstance (reference.Handle, JniHandleOwnership.DoNotTransfer, targetType) as IJavaPeerable; + JniObjectReference.Dispose (ref reference, options); + return peer; + } + public override void AddPeer (IJavaPeerable value) { + if (value == null) + throw new ArgumentNullException (nameof (value)); + if (!value.PeerReference.IsValid) + throw new ArgumentException ("Must have a valid JNI object reference!", nameof (value)); + + var reference = value.PeerReference; + var hash = JNIEnv.IdentityHash (reference.Handle); + + AddPeer (value, reference, hash); + } + + internal void AddPeer (IJavaPeerable value, JniObjectReference reference, IntPtr hash) + { + lock (instances) { + IdentityHashTargets targets; + if (!instances.TryGetValue (hash, out targets)) { + targets = new IdentityHashTargets (value); + instances.Add (hash, targets); + return; + } + bool found = false; + for (int i = 0; i < targets.Count; ++i) { + IJavaPeerable target; + var wref = targets [i]; + if (ShouldReplaceMapping (wref, reference, out target)) { + found = true; + targets [i] = IdentityHashTargets.CreateWeakReference (value); + break; + } + if (JniEnvironment.Types.IsSameObject (value.PeerReference, target.PeerReference)) { + found = true; + if (Logger.LogGlobalRef) { + Logger.Log (LogLevel.Info, "monodroid-gref", + string.Format ("warning: not replacing previous registered handle {0} with handle {1} for key_handle 0x{2}", + target.PeerReference.ToString (), reference.ToString (), hash.ToString ("x"))); + } + } + } + if (!found) { + targets.Add (value); + } + } + } + + internal void AddPeer (IJavaPeerable value, IntPtr handle, JniHandleOwnership transfer, out IntPtr handleField) + { + if (handle == IntPtr.Zero) { + handleField = handle; + return; + } + + var transferType = transfer & (JniHandleOwnership.DoNotTransfer | JniHandleOwnership.TransferLocalRef | JniHandleOwnership.TransferGlobalRef); + switch (transferType) { + case JniHandleOwnership.DoNotTransfer: + handleField = JNIEnv.NewGlobalRef (handle); + break; + case JniHandleOwnership.TransferLocalRef: + handleField = JNIEnv.NewGlobalRef (handle); + JNIEnv.DeleteLocalRef (handle); + break; + case JniHandleOwnership.TransferGlobalRef: + handleField = handle; + break; + default: + throw new ArgumentOutOfRangeException ("transfer", transfer, + "Invalid `transfer` value: " + transfer + " on type " + value.GetType ()); + } + if (handleField == IntPtr.Zero) + throw new InvalidOperationException ("Unable to allocate Global Reference for object '" + value.ToString () + "'!"); + + IntPtr hash = JNIEnv.IdentityHash (handleField); + value.SetJniIdentityHashCode ((int) hash); + if ((transfer & JniHandleOwnership.DoNotRegister) == 0) { + AddPeer (value, new JniObjectReference (handleField, JniObjectReferenceType.Global), hash); + } + + if (Logger.LogGlobalRef) { + JNIEnv._monodroid_gref_log ("handle 0x" + handleField.ToString ("x") + + "; key_handle 0x" + hash.ToString ("x") + + ": Java Type: `" + JNIEnv.GetClassNameFromInstance (handleField) + "`; " + + "MCW type: `" + value.GetType ().FullName + "`\n"); + } + } + + bool ShouldReplaceMapping (WeakReference current, JniObjectReference reference, out IJavaPeerable target) + { + target = null; + + if (current == null) + return true; + + // Target has been GC'd; see also FIXME, above, in finalizer + if (!current.TryGetTarget (out target) || target == null) + return true; + + // It's possible that the instance was GC'd, but the finalizer + // hasn't executed yet, so the `instances` entry is stale. + if (!target.PeerReference.IsValid) + return true; + + if (!JniEnvironment.Types.IsSameObject (target.PeerReference, reference)) + return false; + + // JNIEnv.NewObject/JNIEnv.CreateInstance() compatibility. + // When two MCW's are created for one Java instance [0], + // we want the 2nd MCW to replace the 1st, as the 2nd is + // the one the dev created; the 1st is an implicit intermediary. + // + // [0]: If Java ctor invokes overridden virtual method, we'll + // transition into managed code w/o a registered instance, and + // thus will create an "intermediary" via + // (IntPtr, JniHandleOwnership) .ctor. + if ((target.JniManagedPeerState & JniManagedPeerStates.Replaceable) == JniManagedPeerStates.Replaceable) + return true; + + return false; } public override void RemovePeer (IJavaPeerable value) { + if (value == null) + throw new ArgumentNullException (nameof (value)); + if (!value.PeerReference.IsValid) + throw new ArgumentException ("Must have a valid JNI object reference!", nameof (value)); + + var reference = value.PeerReference; + var hash = JNIEnv.IdentityHash (reference.Handle); + + RemovePeer (value, hash); + } + + internal void RemovePeer (IJavaPeerable value, IntPtr hash) + { + lock (instances) { + IdentityHashTargets targets; + if (!instances.TryGetValue (hash, out targets)) { + return; + } + for (int i = targets.Count - 1; i >= 0; i--) { + var wref = targets [i]; + if (!wref.TryGetTarget (out IJavaPeerable target)) { + // wref is invalidated; remove it. + targets.RemoveAt (i); + continue; + } + if (!object.ReferenceEquals (target, value)) { + continue; + } + targets.RemoveAt (i); + } + if (targets.Count == 0) { + instances.Remove (hash); + } + } } public override IJavaPeerable PeekPeer (JniObjectReference reference) { - return (IJavaPeerable) Java.Lang.Object.GetObject (reference.Handle, JniHandleOwnership.DoNotTransfer); + if (!reference.IsValid) + return null; + + var hash = JNIEnv.IdentityHash (reference.Handle); + lock (instances) { + IdentityHashTargets targets; + if (instances.TryGetValue (hash, out targets)) { + for (int i = targets.Count - 1; i >= 0; i--) { + var wref = targets [i]; + if (!wref.TryGetTarget (out var result) || !result.PeerReference.IsValid) { + targets.RemoveAt (i); + continue; + } + if (!JniEnvironment.Types.IsSameObject (reference, result.PeerReference)) + continue; + return result; + } + } + } + return null; + } + + protected override bool TryUnboxPeerObject (IJavaPeerable value, out object result) + { + var proxy = value as JavaProxyThrowable; + if (proxy != null) { + result = proxy.InnerException; + return true; + } + return base.TryUnboxPeerObject (value, out result); + } + + internal Exception UnboxException (IJavaPeerable value) + { + object r; + if (TryUnboxPeerObject (value, out r) && r is Exception e) { + return e; + } + return null; } public override void CollectPeers () { + GC.Collect (); } public override void FinalizePeer (IJavaPeerable value) { + if (value == null) + throw new ArgumentNullException (nameof (value)); + + if (Logger.LogGlobalRef) { + JNIEnv._monodroid_gref_log ($"Finalizing handle {value.PeerReference}\n"); + } + + // FIXME: need hash cleanup mechanism. + // Finalization occurs after a test of java persistence. If the + // handle still contains a java reference, we can't finalize the + // object and should "resurrect" it. + if (value.PeerReference.IsValid) { + GC.ReRegisterForFinalize (value); + } else { + RemovePeer (value, (IntPtr) value.JniIdentityHashCode); + value.SetPeerReference (new JniObjectReference ()); + value.Finalized (); + } } public override List GetSurfacedPeers () { - return null; + lock (instances) { + var surfacedPeers = new List (instances.Count); + foreach (var e in instances) { + for (int i = 0; i < e.Value.Count; i++) { + var value = e.Value [i]; + surfacedPeers.Add (new JniSurfacedPeerInfo (e.Key.ToInt32 (), value)); + } + } + return surfacedPeers; + } + } + } + + class InstancesKeyComparer : IEqualityComparer + { + + public bool Equals (IntPtr x, IntPtr y) + { + return x == y; + } + + public int GetHashCode (IntPtr value) + { + return value.GetHashCode (); + } + } + + class IdentityHashTargets { + WeakReference first; + List> rest; + + public static WeakReference CreateWeakReference (IJavaPeerable value) + { + return new WeakReference (value, trackResurrection: true); + } + + public IdentityHashTargets (IJavaPeerable value) + { + first = CreateWeakReference (value); + } + + public int Count => (first != null ? 1 : 0) + (rest != null ? rest.Count : 0); + + public WeakReference this [int index] { + get { + if (index == 0) + return first; + index -= 1; + if (rest == null || index >= rest.Count) + return null; + return rest [index]; + } + set { + if (index == 0) { + first = value; + return; + } + index -= 1; + rest [index] = value; + } + } + + public void Add (IJavaPeerable value) + { + if (first == null) { + first = CreateWeakReference (value); + return; + } + if (rest == null) + rest = new List> (); + rest.Add (CreateWeakReference (value)); + } + + public void RemoveAt (int index) + { + if (index == 0) { + first = null; + if (rest?.Count > 0) { + first = rest [0]; + rest.RemoveAt (0); + } + return; + } + index -= 1; + rest.RemoveAt (index); } } } diff --git a/src/Mono.Android/Android.Runtime/InputStreamInvoker.cs b/src/Mono.Android/Android.Runtime/InputStreamInvoker.cs index 484f70207a7..da48df7bda8 100644 --- a/src/Mono.Android/Android.Runtime/InputStreamInvoker.cs +++ b/src/Mono.Android/Android.Runtime/InputStreamInvoker.cs @@ -122,10 +122,10 @@ public static Stream FromJniHandle (IntPtr handle, JniHandleOwnership transfer) if (handle == IntPtr.Zero) return null; - IJavaObject inst = Java.Lang.Object.PeekObject (handle); + IJavaObject inst = (IJavaObject) Java.Lang.Object.PeekObject (handle); if (inst == null) - inst = Java.Interop.TypeManager.CreateInstance (handle, transfer); + inst = (IJavaObject) Java.Interop.TypeManager.CreateInstance (handle, transfer); else JNIEnv.DeleteRef (handle, transfer); diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 98a78239cc9..fe7a10aae70 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -61,6 +61,8 @@ public static partial class JNIEnv { static AndroidRuntime androidRuntime; static BoundExceptionType BoundExceptionType; + internal static AndroidValueManager AndroidValueManager; + [DllImport ("__Internal", CallingConvention = CallingConvention.Cdecl)] extern static void monodroid_log (LogLevel level, LogCategories category, string message); @@ -166,6 +168,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) BoundExceptionType = (BoundExceptionType)args->ioExceptionType; androidRuntime = new AndroidRuntime (args->env, args->javaVm, androidSdkVersion > 10, args->grefLoader, args->Loader_loadClass); + AndroidValueManager = (AndroidValueManager) androidRuntime.ValueManager; AllocObjectSupported = androidSdkVersion > 10; IsRunningOnDesktop = args->isRunningOnDesktop == 1; diff --git a/src/Mono.Android/Android.Runtime/JavaCollection.cs b/src/Mono.Android/Android.Runtime/JavaCollection.cs index 8a0a83cb20f..7eedb3f4ae5 100644 --- a/src/Mono.Android/Android.Runtime/JavaCollection.cs +++ b/src/Mono.Android/Android.Runtime/JavaCollection.cs @@ -178,7 +178,7 @@ public static ICollection FromJniHandle (IntPtr handle, JniHandleOwnership trans if (handle == IntPtr.Zero) return null; - IJavaObject inst = Java.Lang.Object.PeekObject (handle); + IJavaObject inst = (IJavaObject) Java.Lang.Object.PeekObject (handle); if (inst == null) inst = new JavaCollection (handle, transfer); else @@ -397,7 +397,7 @@ public static ICollection FromJniHandle (IntPtr handle, JniHandleOwnership tr if (handle == IntPtr.Zero) return null; - IJavaObject inst = Java.Lang.Object.PeekObject (handle); + IJavaObject inst = (IJavaObject) Java.Lang.Object.PeekObject (handle); if (inst == null) inst = new JavaCollection (handle, transfer); else diff --git a/src/Mono.Android/Android.Runtime/JavaDictionary.cs b/src/Mono.Android/Android.Runtime/JavaDictionary.cs index 49f618ea7c2..4593d334779 100644 --- a/src/Mono.Android/Android.Runtime/JavaDictionary.cs +++ b/src/Mono.Android/Android.Runtime/JavaDictionary.cs @@ -359,7 +359,7 @@ public static IDictionary FromJniHandle (IntPtr handle, JniHandleOwnership trans if (handle == IntPtr.Zero) return null; - IJavaObject inst = Java.Lang.Object.PeekObject (handle); + IJavaObject inst = (IJavaObject) Java.Lang.Object.PeekObject (handle); if (inst == null) inst = new JavaDictionary (handle, transfer); else @@ -630,7 +630,7 @@ public static IDictionary FromJniHandle (IntPtr handle, JniHandleOwnership if (handle == IntPtr.Zero) return null; - IJavaObject inst = Java.Lang.Object.PeekObject (handle); + IJavaObject inst = (IJavaObject) Java.Lang.Object.PeekObject (handle); if (inst == null) inst = new JavaDictionary (handle, transfer); else diff --git a/src/Mono.Android/Android.Runtime/JavaList.cs b/src/Mono.Android/Android.Runtime/JavaList.cs index b260b101e78..f771ef8f6f0 100644 --- a/src/Mono.Android/Android.Runtime/JavaList.cs +++ b/src/Mono.Android/Android.Runtime/JavaList.cs @@ -480,7 +480,7 @@ public static IList FromJniHandle (IntPtr handle, JniHandleOwnership transfer) if (handle == IntPtr.Zero) return null; - IJavaObject inst = Java.Lang.Object.PeekObject (handle); + IJavaObject inst = (IJavaObject) Java.Lang.Object.PeekObject (handle); if (inst == null) inst = new JavaList (handle, transfer); else @@ -936,7 +936,7 @@ public static IList FromJniHandle (IntPtr handle, JniHandleOwnership transfer if (handle == IntPtr.Zero) return null; - IJavaObject inst = Java.Lang.Object.PeekObject (handle, typeof (IList)); + IJavaObject inst = (IJavaObject) Java.Lang.Object.PeekObject (handle, typeof (IList)); if (inst == null) inst = new JavaList (handle, transfer); else diff --git a/src/Mono.Android/Android.Runtime/JavaSet.cs b/src/Mono.Android/Android.Runtime/JavaSet.cs index d01d4eca4bf..ccf9a03a985 100644 --- a/src/Mono.Android/Android.Runtime/JavaSet.cs +++ b/src/Mono.Android/Android.Runtime/JavaSet.cs @@ -243,7 +243,7 @@ public static ICollection FromJniHandle (IntPtr handle, JniHandleOwnership trans if (handle == IntPtr.Zero) return null; - IJavaObject inst = Java.Lang.Object.PeekObject (handle); + IJavaObject inst = (IJavaObject) Java.Lang.Object.PeekObject (handle); if (inst == null) inst = new JavaSet (handle, transfer); else @@ -429,7 +429,7 @@ public static ICollection FromJniHandle (IntPtr handle, JniHandleOwnership tr if (handle == IntPtr.Zero) return null; - IJavaObject inst = Java.Lang.Object.PeekObject (handle); + IJavaObject inst = (IJavaObject) Java.Lang.Object.PeekObject (handle); if (inst == null) inst = new JavaSet (handle, transfer); else diff --git a/src/Mono.Android/Android.Runtime/OutputStreamInvoker.cs b/src/Mono.Android/Android.Runtime/OutputStreamInvoker.cs index 4a15b45df6c..b192cf607fa 100644 --- a/src/Mono.Android/Android.Runtime/OutputStreamInvoker.cs +++ b/src/Mono.Android/Android.Runtime/OutputStreamInvoker.cs @@ -136,10 +136,10 @@ internal static Stream FromNative (IntPtr handle, JniHandleOwnership transfer) if (handle == IntPtr.Zero) return null; - IJavaObject inst = Java.Lang.Object.PeekObject (handle); + IJavaObject inst = (IJavaObject) Java.Lang.Object.PeekObject (handle); if (inst == null) - inst = Java.Interop.TypeManager.CreateInstance (handle, transfer); + inst = (IJavaObject) Java.Interop.TypeManager.CreateInstance (handle, transfer); else JNIEnv.DeleteRef (handle, transfer); diff --git a/src/Mono.Android/Android.Runtime/XmlPullParserReader.cs b/src/Mono.Android/Android.Runtime/XmlPullParserReader.cs index 1bc5102633a..32a5f525bad 100644 --- a/src/Mono.Android/Android.Runtime/XmlPullParserReader.cs +++ b/src/Mono.Android/Android.Runtime/XmlPullParserReader.cs @@ -35,9 +35,9 @@ static XmlResourceParserReader FromNative (IntPtr handle, JniHandleOwnership tra { if (handle == IntPtr.Zero) return null; - IJavaObject inst = Java.Lang.Object.PeekObject (handle); + IJavaObject inst = (IJavaObject) Java.Lang.Object.PeekObject (handle); if (inst == null) - inst = Java.Interop.TypeManager.CreateInstance (handle, transfer); + inst = (IJavaObject) Java.Interop.TypeManager.CreateInstance (handle, transfer); else JNIEnv.DeleteRef (handle, transfer); return new XmlResourceParserReader (inst.JavaCast ()); @@ -392,9 +392,9 @@ static XmlReader FromNative (IntPtr handle, JniHandleOwnership transfer) { if (handle == IntPtr.Zero) return null; - IJavaObject inst = Java.Lang.Object.PeekObject (handle); + IJavaObject inst = (IJavaObject) Java.Lang.Object.PeekObject (handle); if (inst == null) - inst = Java.Interop.TypeManager.CreateInstance (handle, transfer); + inst = (IJavaObject) Java.Interop.TypeManager.CreateInstance (handle, transfer); else JNIEnv.DeleteRef (handle, transfer); return new XmlPullParserReader (inst.JavaCast ()); diff --git a/src/Mono.Android/Java.Interop/JavaConvert.cs b/src/Mono.Android/Java.Interop/JavaConvert.cs index fb1a3c76e01..399a59a18ec 100644 --- a/src/Mono.Android/Java.Interop/JavaConvert.cs +++ b/src/Mono.Android/Java.Interop/JavaConvert.cs @@ -104,7 +104,7 @@ public static T FromJniHandle(IntPtr handle, JniHandleOwnership transfer, out return default (T); } - IJavaObject interned = Java.Lang.Object.PeekObject (handle); + IJavaObject interned = (IJavaObject) Java.Lang.Object.PeekObject (handle); if (interned != null) { T r = FromJavaObject(interned, out set); if (set) { @@ -138,7 +138,7 @@ public static object FromJniHandle (IntPtr handle, JniHandleOwnership transfer, return null; } - IJavaObject interned = Java.Lang.Object.PeekObject (handle); + IJavaObject interned = (IJavaObject) Java.Lang.Object.PeekObject (handle); if (interned != null) { var unwrapped = FromJavaObject (interned, targetType); if (unwrapped != null) { diff --git a/src/Mono.Android/Java.Interop/JavaObjectExtensions.cs b/src/Mono.Android/Java.Interop/JavaObjectExtensions.cs index 2a67b624367..90516e0ba0a 100644 --- a/src/Mono.Android/Java.Interop/JavaObjectExtensions.cs +++ b/src/Mono.Android/Java.Interop/JavaObjectExtensions.cs @@ -109,7 +109,7 @@ internal static IJavaObject JavaCast (IJavaObject instance, Type resultType) return CastClass (instance, resultType); } else if (resultType.IsInterface) { - return Java.Lang.Object.GetObject (instance.Handle, JniHandleOwnership.DoNotTransfer, resultType); + return (IJavaObject) Java.Lang.Object.GetObject (instance.Handle, JniHandleOwnership.DoNotTransfer, resultType); } else throw new NotSupportedException (string.Format ("Unable to convert type '{0}' to '{1}'.", diff --git a/src/Mono.Android/Java.Interop/Runtime.cs b/src/Mono.Android/Java.Interop/Runtime.cs index b4e28f584fa..50703a35925 100644 --- a/src/Mono.Android/Java.Interop/Runtime.cs +++ b/src/Mono.Android/Java.Interop/Runtime.cs @@ -8,9 +8,16 @@ namespace Java.Interop { public static class Runtime { + [Obsolete ("Please use Java.Interop.JniEnvironment.Runtime.ValueManager.GetSurfacedPeers()")] public static List GetSurfacedObjects () { - return Java.Lang.Object.GetSurfacedObjects_ForDiagnosticsOnly (); + var peers = JNIEnv.AndroidValueManager.GetSurfacedPeers (); + var r = new List (peers.Count); + foreach (var p in peers) { + if (p.SurfacedPeer.TryGetTarget (out var target)) + r.Add (new WeakReference (target, trackResurrection: true)); + } + return r; } [DllImport ("__Internal", CallingConvention = CallingConvention.Cdecl)] diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index 98efa0c5a09..0cf4265f62c 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -236,12 +236,12 @@ internal static Type GetJavaToManagedType (string class_name) return null; } - internal static IJavaObject CreateInstance (IntPtr handle, JniHandleOwnership transfer) + internal static IJavaPeerable CreateInstance (IntPtr handle, JniHandleOwnership transfer) { return CreateInstance (handle, transfer, null); } - internal static IJavaObject CreateInstance (IntPtr handle, JniHandleOwnership transfer, Type targetType) + internal static IJavaPeerable CreateInstance (IntPtr handle, JniHandleOwnership transfer, Type targetType) { Type type = null; IntPtr class_ptr = JNIEnv.GetObjectClass (handle); @@ -284,13 +284,13 @@ internal static IJavaObject CreateInstance (IntPtr handle, JniHandleOwnership tr } - IJavaObject result = null; + IJavaPeerable result = null; try { - result = (IJavaObject) CreateProxy (type, handle, transfer); - var ex = result as IJavaObjectEx; - if (Runtime.IsGCUserPeer (result) && ex != null) - ex.IsProxy = true; + result = (IJavaPeerable) CreateProxy (type, handle, transfer); + if (Runtime.IsGCUserPeer (result.PeerReference.Handle)) { + result.SetJniManagedPeerState (JniManagedPeerStates.Replaceable); + } } catch (MissingMethodException e) { var key_handle = JNIEnv.IdentityHash (handle); JNIEnv.DeleteRef (handle, transfer); @@ -302,18 +302,30 @@ internal static IJavaObject CreateInstance (IntPtr handle, JniHandleOwnership tr return result; } + static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) }; + static readonly Type[] JIConstructorSignature = new Type [] { typeof (JniObjectReference).MakeByRefType (), typeof (JniObjectReferenceOptions) }; + internal static object CreateProxy (Type type, IntPtr handle, JniHandleOwnership transfer) { // Skip Activator.CreateInstance() as that requires public constructors, // and we want to hide some constructors for sanity reasons. BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - ConstructorInfo c = type.GetConstructor (flags, null, new[]{typeof (IntPtr), typeof (JniHandleOwnership)}, null); - if (c == null) { - throw new MissingMethodException ( - "No constructor found for " + type.FullName + "::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)", - CreateJavaLocationException ()); + ConstructorInfo c = type.GetConstructor (flags, null, XAConstructorSignature, null); + if (c != null) { + return c.Invoke (new object [] { handle, transfer }); + } + c = type.GetConstructor (flags, null, JIConstructorSignature, null); + if (c != null) { + JniObjectReference r = new JniObjectReference (handle); + JniObjectReferenceOptions o = JniObjectReferenceOptions.Copy; + var peer = (IJavaPeerable) c.Invoke (new object [] { r, o }); + JNIEnv.DeleteRef (handle, transfer); + peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable); + return peer; } - return c.Invoke (new object[]{handle, transfer}); + throw new MissingMethodException ( + "No constructor found for " + type.FullName + "::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)", + CreateJavaLocationException ()); } public static void RegisterType (string java_class, Type t) diff --git a/src/Mono.Android/Java.Lang/Object.cs b/src/Mono.Android/Java.Lang/Object.cs index 496a235b1ce..b9a084788ce 100644 --- a/src/Mono.Android/Java.Lang/Object.cs +++ b/src/Mono.Android/Java.Lang/Object.cs @@ -15,9 +15,6 @@ public partial class Object : IDisposable, IJavaObject, IJavaObjectEx , IJavaPeerable #endif // JAVA_INTEROP { - - static Dictionary> instances = new Dictionary> (new InstancesKeyComparer ()); - IntPtr key_handle; IntPtr weak_handle; JObjectRefType handle_type; @@ -52,21 +49,15 @@ IntPtr IJavaObjectEx.ToLocalJniHandle () ~Object () { - if (Logger.LogGlobalRef) { - JNIEnv._monodroid_gref_log ( - string.Format ("Finalizing handle 0x{0}\n", handle.ToString ("x"))); - } // FIXME: need hash cleanup mechanism. // Finalization occurs after a test of java persistence. If the // handle still contains a java reference, we can't finalize the // object and should "resurrect" it. refs_added = 0; - if (handle != IntPtr.Zero) - GC.ReRegisterForFinalize (this); - else { - Dispose (false); - DeregisterInstance (this, key_handle); + if (Environment.HasShutdownStarted) { + return; } + JniEnvironment.Runtime.ValueManager.FinalizePeer (this); } public Object (IntPtr handle, JniHandleOwnership transfer) @@ -163,17 +154,17 @@ void IJavaPeerable.DisposeUnlessReferenced () public void UnregisterFromRuntime () { - DeregisterInstance (this, key_handle); + JNIEnv.AndroidValueManager.RemovePeer (this, key_handle); } void IJavaPeerable.Disposed () { - throw new NotSupportedException (); + Dispose (disposing: true); } void IJavaPeerable.Finalized () { - throw new NotSupportedException (); + Dispose (disposing: false); } void IJavaPeerable.SetJniIdentityHashCode (int value) @@ -200,16 +191,14 @@ void IJavaPeerable.SetPeerReference (JniObjectReference reference) public void Dispose () { - Dispose (true); - Dispose (this, ref handle, key_handle, handle_type); - GC.SuppressFinalize (this); + JNIEnv.AndroidValueManager.DisposePeer (this); } protected virtual void Dispose (bool disposing) { } - internal static void Dispose (object instance, ref IntPtr handle, IntPtr key_handle, JObjectRefType handle_type) + internal static void Dispose (IJavaPeerable instance, ref IntPtr handle, IntPtr key_handle, JObjectRefType handle_type) { if (handle == IntPtr.Zero) return; @@ -219,7 +208,7 @@ internal static void Dispose (object instance, ref IntPtr handle, IntPtr key_han string.Format ("Disposing handle 0x{0}\n", handle.ToString ("x"))); } - DeregisterInstance (instance, key_handle); + JNIEnv.AndroidValueManager.RemovePeer (instance, key_handle); switch (handle_type) { case JObjectRefType.Global: @@ -242,174 +231,18 @@ internal static void Dispose (object instance, ref IntPtr handle, IntPtr key_han protected void SetHandle (IntPtr value, JniHandleOwnership transfer) { - RegisterInstance (this, value, transfer, out handle); + JNIEnv.AndroidValueManager.AddPeer (this, value, transfer, out handle); handle_type = JObjectRefType.Global; } - internal static void RegisterInstance (IJavaObject instance, IntPtr value, JniHandleOwnership transfer, out IntPtr handle) - { - if (value == IntPtr.Zero) { - handle = value; - return; - } - - var transferType = transfer & (JniHandleOwnership.DoNotTransfer | JniHandleOwnership.TransferLocalRef | JniHandleOwnership.TransferGlobalRef); - switch (transferType) { - case JniHandleOwnership.DoNotTransfer: - handle = JNIEnv.NewGlobalRef (value); - break; - case JniHandleOwnership.TransferLocalRef: - handle = JNIEnv.NewGlobalRef (value); - JNIEnv.DeleteLocalRef (value); - break; - case JniHandleOwnership.TransferGlobalRef: - handle = value; - break; - default: - throw new ArgumentOutOfRangeException ("transfer", transfer, - "Invalid `transfer` value: " + transfer + " on type " + instance.GetType ()); - } - if (handle == IntPtr.Zero) - throw new InvalidOperationException ("Unable to allocate Global Reference for object '" + instance.ToString () + "'!"); - - IntPtr key = JNIEnv.IdentityHash (handle); - if ((transfer & JniHandleOwnership.DoNotRegister) == 0) { - _RegisterInstance (instance, key, handle); - } - var ex = instance as IJavaObjectEx; - if (ex != null) - ex.KeyHandle = key; - - if (Logger.LogGlobalRef) { - JNIEnv._monodroid_gref_log ("handle 0x" + handle.ToString ("x") + - "; key_handle 0x" + key.ToString ("x") + - ": Java Type: `" + JNIEnv.GetClassNameFromInstance (handle) + "`; " + - "MCW type: `" + instance.GetType ().FullName + "`\n"); - } - } - - static void _RegisterInstance (IJavaObject instance, IntPtr key, IntPtr handle) - { - lock (instances) { - List wrefs; - if (!instances.TryGetValue (key, out wrefs)) { - wrefs = new List (1) { - new WeakReference (instance, true), - }; - instances.Add (key, wrefs); - } - else { - bool found = false; - for (int i = 0; i < wrefs.Count; ++i) { - var wref = wrefs [i]; - if (ShouldReplaceMapping (wref, handle)) { - found = true; - wrefs.Remove (wref); - wrefs.Add (new WeakReference (instance, true)); - break; - } - var cur = wref == null ? null : (IJavaObject) wref.Target; - var _c = cur == null ? IntPtr.Zero : cur.Handle; - if (_c != IntPtr.Zero && JNIEnv.IsSameObject (handle, _c)) { - found = true; - if (Logger.LogGlobalRef) { - Logger.Log (LogLevel.Info, "monodroid-gref", - string.Format ("warning: not replacing previous registered handle 0x{0} with handle 0x{1} for key_handle 0x{2}", - _c.ToString ("x"), handle.ToString ("x"), key.ToString ("x"))); - } - break; - } - } - if (!found) { - wrefs.Add (new WeakReference (instance, true)); - } - } - } - } - - static bool ShouldReplaceMapping (WeakReference current, IntPtr handle) + internal static IJavaPeerable PeekObject (IntPtr handle, Type requiredType = null) { - if (current == null) - return true; - - // Target has been GC'd; see also FIXME, above, in finalizer - object target = current.Target; - if (target == null) - return true; - - // It's possible that the instance was GC'd, but the finalizer - // hasn't executed yet, so the `instances` entry is stale. - var ijo = (IJavaObject) target; - if (ijo.Handle == IntPtr.Zero) - return true; - - if (!JNIEnv.IsSameObject (ijo.Handle, handle)) - return false; - - // JNIEnv.NewObject/JNIEnv.CreateInstance() compatibility. - // When two MCW's are created for one Java instance [0], - // we want the 2nd MCW to replace the 1st, as the 2nd is - // the one the dev created; the 1st is an implicit intermediary. - // - // [0]: If Java ctor invokes overridden virtual method, we'll - // transition into managed code w/o a registered instance, and - // thus will create an "intermediary" via - // (IntPtr, JniHandleOwnership) .ctor. - var ex = target as IJavaObjectEx; - if (ex != null && ex.IsProxy) - return true; - - return false; - } - - internal static void DeregisterInstance (object instance, IntPtr key_handle) - { - lock (instances) { - List wrefs; - if (instances.TryGetValue (key_handle, out wrefs)) { - for (int i = wrefs.Count-1; i >= 0; --i) { - var wref = wrefs [i]; - if (wref.Target == null || wref.Target == instance) { - wrefs.RemoveAt (i); - } - } - if (wrefs.Count == 0) - instances.Remove (key_handle); - } - } - } - - internal static List GetSurfacedObjects_ForDiagnosticsOnly () - { - lock (instances) { - var surfaced = new List (instances.Count); - foreach (var e in instances) { - surfaced.AddRange (e.Value); - } - return surfaced; - } - } - - internal static IJavaObject PeekObject (IntPtr handle, Type requiredType = null) - { - if (handle == IntPtr.Zero) + var peeked = JNIEnv.AndroidValueManager.PeekPeer (new JniObjectReference (handle)); + if (peeked == null) return null; - - lock (instances) { - List wrefs; - if (instances.TryGetValue (JNIEnv.IdentityHash (handle), out wrefs)) { - for (int i = 0; i < wrefs.Count; ++i) { - var wref = wrefs [i]; - IJavaObject res = wref.Target as IJavaObject; - if (res != null && res.Handle != IntPtr.Zero && JNIEnv.IsSameObject (handle, res.Handle)) { - if (requiredType != null && !requiredType.IsAssignableFrom (res.GetType ())) - return null; - return res; - } - } - } - } - return null; + if (requiredType != null && !requiredType.IsAssignableFrom (peeked.GetType ())) + return null; + return peeked; } internal static T PeekObject (IntPtr handle) @@ -438,30 +271,15 @@ internal static T _GetObject (IntPtr handle, JniHandleOwnership transfer) return (T) GetObject (handle, transfer, typeof (T)); } - internal static IJavaObject GetObject (IntPtr handle, JniHandleOwnership transfer, Type type = null) + internal static IJavaPeerable GetObject (IntPtr handle, JniHandleOwnership transfer, Type type = null) { if (handle == IntPtr.Zero) return null; - lock (instances) { - List wrefs; - if (instances.TryGetValue (JNIEnv.IdentityHash (handle), out wrefs)) { - for (int i = 0; i < wrefs.Count; ++i) { - var wref = wrefs [i]; - var result = wref.Target as IJavaObject; - var exists = result != null && result.Handle != IntPtr.Zero && JNIEnv.IsSameObject (handle, result.Handle); - if (exists) { - if (type == null ? true : type.IsAssignableFrom (result.GetType ())) { - JNIEnv.DeleteRef (handle, transfer); - return result; - } - /* - Logger.Log (LogLevel.Warn, "*jonp*", "# jonp: Object.GetObject: handle=0x" + handle.ToString ("x") + " found but is of type '" + result.GetType ().FullName + - "' and not the required targetType of '" + type + "'."); - */ - } - } - } + var r = PeekObject (handle, type); + if (r != null) { + JNIEnv.DeleteRef (handle, transfer); + return r; } return Java.Interop.TypeManager.CreateInstance (handle, transfer, type); @@ -725,17 +543,4 @@ public static explicit operator string[] (Java.Lang.Object value) return value.ToArray(); } } - - class InstancesKeyComparer : IEqualityComparer { - - public bool Equals (IntPtr x, IntPtr y) - { - return x == y; - } - - public int GetHashCode (IntPtr value) - { - return value.GetHashCode (); - } - } } diff --git a/src/Mono.Android/Java.Lang/Throwable.cs b/src/Mono.Android/Java.Lang/Throwable.cs index d562565096d..504b658b08b 100644 --- a/src/Mono.Android/Java.Lang/Throwable.cs +++ b/src/Mono.Android/Java.Lang/Throwable.cs @@ -226,7 +226,7 @@ public unsafe Java.Lang.Class Class { protected void SetHandle (IntPtr value, JniHandleOwnership transfer) { - Java.Lang.Object.RegisterInstance (this, value, transfer, out handle); + JNIEnv.AndroidValueManager.AddPeer (this, value, transfer, out handle); handle_type = JObjectRefType.Global; } @@ -251,18 +251,11 @@ public static System.Exception ToException (Throwable e) ~Throwable () { - if (Logger.LogGlobalRef) { - JNIEnv._monodroid_gref_log ( - string.Format ("Finalizing Throwable handle 0x{0}\n", handle.ToString ("x"))); - } - refs_added = 0; - if (handle != IntPtr.Zero) - GC.ReRegisterForFinalize (this); - else { - Dispose (false); - Object.DeregisterInstance (this, key_handle); + if (Environment.HasShutdownStarted) { + return; } + JniEnvironment.Runtime.ValueManager.FinalizePeer (this); } #if JAVA_INTEROP @@ -288,17 +281,17 @@ void IJavaPeerable.DisposeUnlessReferenced () public void UnregisterFromRuntime () { - Object.DeregisterInstance (this, key_handle); + JNIEnv.AndroidValueManager.RemovePeer (this, key_handle); } void IJavaPeerable.Disposed () { - throw new NotSupportedException (); + Dispose (disposing: true); } void IJavaPeerable.Finalized () { - throw new NotSupportedException (); + Dispose (disposing: false); } void IJavaPeerable.SetJniIdentityHashCode (int value) @@ -324,10 +317,7 @@ void IJavaPeerable.SetPeerReference (JniObjectReference reference) public void Dispose () { - Dispose (true); - Java.Lang.Object.Dispose (this, ref handle, key_handle, handle_type); - key_handle = IntPtr.Zero; - GC.SuppressFinalize (this); + JNIEnv.AndroidValueManager.DisposePeer (this); } protected virtual void Dispose (bool disposing) diff --git a/src/Mono.Android/Test/Java.Interop-Tests/.gitignore b/src/Mono.Android/Test/Java.Interop-Tests/.gitignore new file mode 100644 index 00000000000..d996c0b7286 --- /dev/null +++ b/src/Mono.Android/Test/Java.Interop-Tests/.gitignore @@ -0,0 +1 @@ +Jars diff --git a/src/Mono.Android/Test/Java.Interop-Tests/Java.Interop-Tests.csproj b/src/Mono.Android/Test/Java.Interop-Tests/Java.Interop-Tests.csproj new file mode 100644 index 00000000000..a587304a775 --- /dev/null +++ b/src/Mono.Android/Test/Java.Interop-Tests/Java.Interop-Tests.csproj @@ -0,0 +1,90 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {6CB00820-A66B-43E5-8785-ED456C6E9F39} + {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {9ef11e43-1701-4396-8835-8392d57abb70} + Library + Properties + Java.Interop_Tests + Java.Interop-Tests + 512 + Resources\Resource.designer.cs + Off + v10.0 + true + + + + $(AndroidFrameworkVersion) + true + portable + false + bin\Debug\ + DEBUG;TRACE;NO_MARSHAL_MEMBER_BUILDER_SUPPORT + prompt + 4 + + + $(AndroidFrameworkVersion) + + true + bin\Release\ + TRACE;NO_MARSHAL_MEMBER_BUILDER_SUPPORT + prompt + 4 + + + + + + + + + + + + ..\..\..\..\packages\mono.linq.expressions.2.0.0\lib\netstandard2.0\Mono.Linq.Expressions.dll + + + + + + + + + + + + + + + + + + + {D1243BAB-23CA-4566-A2A3-3ADA2C2DC3AF} + Java.Interop.GenericMarshaler + + + + + + + BuildTestJarFile; + _CopyTestJarFiles; + $(BuildDependsOn) + + + + + CleanTestJarFile; + $(CleanDependsOn); + CleanLocal; + + + \ No newline at end of file diff --git a/src/Mono.Android/Test/Java.Interop-Tests/Java.Interop-Tests.targets b/src/Mono.Android/Test/Java.Interop-Tests/Java.Interop-Tests.targets new file mode 100644 index 00000000000..c6a3371fcc7 --- /dev/null +++ b/src/Mono.Android/Test/Java.Interop-Tests/Java.Interop-Tests.targets @@ -0,0 +1,24 @@ + + + + + + + + + Jars\Mono.Android-Test-classes.jar + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Mono.Android/Test/Java.Interop-Tests/Java.InteropTests/JavaInterop_Tests_Reference.cs b/src/Mono.Android/Test/Java.Interop-Tests/Java.InteropTests/JavaInterop_Tests_Reference.cs new file mode 100644 index 00000000000..e0a898e5cd8 --- /dev/null +++ b/src/Mono.Android/Test/Java.Interop-Tests/Java.InteropTests/JavaInterop_Tests_Reference.cs @@ -0,0 +1,9 @@ +using System; + +namespace Java.InteropTests +{ + // Exists for "easy" reference by Mono.Android-Tests.dll + public class JavaInterop_Tests_Reference + { + } +} diff --git a/src/Mono.Android/Test/Java.Interop-Tests/Properties/AssemblyInfo.cs b/src/Mono.Android/Test/Java.Interop-Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..887802784b2 --- /dev/null +++ b/src/Mono.Android/Test/Java.Interop-Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Android.App; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Java.Interop_Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Java.Interop_Tests")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Mono.Android/Test/Java.Interop-Tests/Resources/AboutResources.txt b/src/Mono.Android/Test/Java.Interop-Tests/Resources/AboutResources.txt new file mode 100644 index 00000000000..c2bca974c48 --- /dev/null +++ b/src/Mono.Android/Test/Java.Interop-Tests/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. \ No newline at end of file diff --git a/src/Mono.Android/Test/Java.Interop-Tests/packages.config b/src/Mono.Android/Test/Java.Interop-Tests/packages.config new file mode 100644 index 00000000000..1c3a8d1b1f6 --- /dev/null +++ b/src/Mono.Android/Test/Java.Interop-Tests/packages.config @@ -0,0 +1,4 @@ + + + + \ 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 0d018ae3ea2..96118c34a18 100644 --- a/src/Mono.Android/Test/Mono.Android-Tests.csproj +++ b/src/Mono.Android/Test/Mono.Android-Tests.csproj @@ -60,6 +60,9 @@ + + + @@ -87,6 +90,10 @@ {CB2335CB-0050-4020-8A05-E9614EDAA05E} TestRunner.NUnit + + {6CB00820-A66B-43E5-8785-ED456C6E9F39} + Java.Interop-Tests + {8CB5FF58-FF95-43B9-9064-9ACE9525866F} Mono.Android-Test.Library diff --git a/src/Mono.Android/Test/Properties/AndroidManifest.xml b/src/Mono.Android/Test/Properties/AndroidManifest.xml index d15674daf9a..a67c4caa579 100644 --- a/src/Mono.Android/Test/Properties/AndroidManifest.xml +++ b/src/Mono.Android/Test/Properties/AndroidManifest.xml @@ -1,7 +1,7 @@  - + diff --git a/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs b/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs index 2da4d177c7b..0a58cd6bc81 100644 --- a/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs +++ b/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs @@ -30,10 +30,13 @@ protected NUnitInstrumentation(IntPtr handle, JniHandleOwnership transfer) protected override IList GetTestAssemblies() { Assembly asm = Assembly.GetExecutingAssembly(); + Assembly ji = typeof (Java.InteropTests.JavaInterop_Tests_Reference).Assembly; + return new List() { - new TestAssemblyInfo(asm, asm.Location ?? String.Empty) + new TestAssemblyInfo (asm, asm.Location ?? String.Empty), + new TestAssemblyInfo (ji, ji.Location ?? String.Empty), }; } } diff --git a/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/TestInstrumentation.cs b/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/TestInstrumentation.cs index e3dbc895242..1dfae2ed6af 100644 --- a/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/TestInstrumentation.cs +++ b/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/TestInstrumentation.cs @@ -21,6 +21,7 @@ public TestInstrumentation (IntPtr handle, JniHandleOwnership transfer) protected override void AddTests () { AddTest (Assembly.GetExecutingAssembly ()); + AddTest (typeof (Java.InteropTests.JavaInterop_Tests_Reference).Assembly); } } } diff --git a/src/Mono.Android/Test/proguard.cfg b/src/Mono.Android/Test/proguard.cfg new file mode 100644 index 00000000000..cbc69c8bebe --- /dev/null +++ b/src/Mono.Android/Test/proguard.cfg @@ -0,0 +1,3 @@ +# Need to preserve the contents of Mono.Android-Test-classes.jar + +-keep class com.xamarin.interop.** { *; (); } diff --git a/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Java.Interop.xml b/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Java.Interop.xml new file mode 100644 index 00000000000..bfb2cfa1802 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Java.Interop.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml b/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml index c6da83bffdf..3c4d22fbfe0 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml +++ b/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml @@ -47,6 +47,18 @@ --> + + + + + + + + + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Resources/proguard_xamarin.cfg b/src/Xamarin.Android.Build.Tasks/Resources/proguard_xamarin.cfg index 695f410aa22..4629e018dd5 100644 --- a/src/Xamarin.Android.Build.Tasks/Resources/proguard_xamarin.cfg +++ b/src/Xamarin.Android.Build.Tasks/Resources/proguard_xamarin.cfg @@ -3,6 +3,7 @@ -dontobfuscate -keep class android.support.multidex.MultiDexApplication { (); } +-keep class com.xamarin.java_interop.** { *; (); } -keep class mono.MonoRuntimeProvider { *; (...); } -keep class mono.MonoPackageManager { *; (...); } -keep class mono.MonoPackageManager_Resources { *; (...); } 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 104bf1c41f2..75de516c1b7 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -625,6 +625,9 @@ MonoRuntimeProvider.Shared.20.java + + Java.Interop.xml + mscorlib.xml diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in index c03391c09b3..1277d2b5aac 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in @@ -19,6 +19,7 @@ 26.1.1 16.1 @BUNDLETOOL_VERSION@ + <_XamarinAndroidMSBuildDirectory>$(MSBuildThisFileDirectory) true diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index b7420ccff6e..34874dbb143 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -903,6 +903,16 @@ MonodroidRuntime::lookup_bridge_info (MonoDomain *domain, MonoImage *image, cons info->handle_type = mono_class_get_field_from_name (info->klass, const_cast ("handle_type")); info->refs_added = mono_class_get_field_from_name (info->klass, const_cast ("refs_added")); info->weak_handle = mono_class_get_field_from_name (info->klass, const_cast ("weak_handle")); + if (info->klass == NULL || info->handle == NULL || info->handle_type == NULL || + info->refs_added == NULL || info->weak_handle == NULL) { + log_fatal (LOG_DEFAULT, "The type `%s.%s` is missing required instance fields! handle=%p handle_type=%p refs_added=%p weak_handle=%p", + type->_namespace, type->_typename, + info->handle, + info->handle_type, + info->refs_added, + info->weak_handle); + exit (FATAL_EXIT_MONO_MISSING_SYMBOLS); + } } void @@ -932,7 +942,9 @@ MonodroidRuntime::init_android_runtime (MonoDomain *domain, JNIEnv *env, jclass MonoAssembly *assm = utils.monodroid_load_assembly (domain, "Mono.Android"); MonoImage *image = mono_assembly_get_image (assm); - for (uint32_t i = 0; i < OSBridge::NUM_GC_BRIDGE_TYPES; ++i) { + uint32_t i = 0; + + for ( ; i < OSBridge::NUM_XA_GC_BRIDGE_TYPES; ++i) { lookup_bridge_info (domain, image, &osBridge.get_java_gc_bridge_type (i), &osBridge.get_java_gc_bridge_info (i)); } @@ -944,6 +956,13 @@ MonodroidRuntime::init_android_runtime (MonoDomain *domain, JNIEnv *env, jclass log_fatal (LOG_DEFAULT, "INTERNAL ERROR: Unable to find Android.Runtime.JNIEnv.Initialize!"); exit (FATAL_EXIT_MISSING_INIT); } + + MonoAssembly *ji_assm = utils.monodroid_load_assembly (domain, "Java.Interop"); + MonoImage *ji_image = mono_assembly_get_image (ji_assm); + for ( ; i < OSBridge::NUM_XA_GC_BRIDGE_TYPES + OSBridge::NUM_JI_GC_BRIDGE_TYPES; ++i) { + lookup_bridge_info (domain, ji_image, &osBridge.get_java_gc_bridge_type (i), &osBridge.get_java_gc_bridge_info (i)); + } + /* If running on desktop, we may be swapping in a new Mono.Android image when calling this * so always make sure we have the freshest handle to the method. */ diff --git a/src/monodroid/jni/osbridge.cc b/src/monodroid/jni/osbridge.cc index b3b72c45d92..dcbe73c131a 100644 --- a/src/monodroid/jni/osbridge.cc +++ b/src/monodroid/jni/osbridge.cc @@ -37,17 +37,25 @@ FILE *lref_log; using namespace xamarin::android; using namespace xamarin::android::internal; -const OSBridge::MonoJavaGCBridgeType OSBridge::mono_java_gc_bridge_types[] = { +const OSBridge::MonoJavaGCBridgeType OSBridge::mono_xa_gc_bridge_types[] = { { "Java.Lang", "Object" }, { "Java.Lang", "Throwable" }, }; +const OSBridge::MonoJavaGCBridgeType OSBridge::mono_ji_gc_bridge_types[] = { + { "Java.Interop", "JavaObject" }, + { "Java.Interop", "JavaException" }, +}; + const OSBridge::MonoJavaGCBridgeType OSBridge::empty_bridge_type = { "", "" }; -const uint32_t OSBridge::NUM_GC_BRIDGE_TYPES = (sizeof (mono_java_gc_bridge_types)/sizeof (mono_java_gc_bridge_types [0])); +const uint32_t OSBridge::NUM_XA_GC_BRIDGE_TYPES = (sizeof (mono_xa_gc_bridge_types)/sizeof (mono_xa_gc_bridge_types [0])); +const uint32_t OSBridge::NUM_JI_GC_BRIDGE_TYPES = (sizeof (mono_ji_gc_bridge_types)/sizeof (mono_ji_gc_bridge_types [0])); +const uint32_t OSBridge::NUM_GC_BRIDGE_TYPES = NUM_XA_GC_BRIDGE_TYPES + NUM_JI_GC_BRIDGE_TYPES; + OSBridge::MonoJavaGCBridgeInfo OSBridge::mono_java_gc_bridge_info [NUM_GC_BRIDGE_TYPES]; OSBridge::MonoJavaGCBridgeInfo OSBridge::empty_bridge_info = { diff --git a/src/monodroid/jni/osbridge.hh b/src/monodroid/jni/osbridge.hh index 0452314e2a7..3c2e99b5568 100644 --- a/src/monodroid/jni/osbridge.hh +++ b/src/monodroid/jni/osbridge.hh @@ -55,11 +55,14 @@ namespace xamarin::android::internal using MonodroidGCTakeRefFunc = mono_bool (OSBridge::*) (JNIEnv *env, MonoObject *obj); static const MonoJavaGCBridgeType empty_bridge_type; - static const MonoJavaGCBridgeType mono_java_gc_bridge_types[]; + static const MonoJavaGCBridgeType mono_xa_gc_bridge_types[]; + static const MonoJavaGCBridgeType mono_ji_gc_bridge_types[]; static MonoJavaGCBridgeInfo empty_bridge_info; static MonoJavaGCBridgeInfo mono_java_gc_bridge_info []; public: + static const uint32_t NUM_XA_GC_BRIDGE_TYPES; + static const uint32_t NUM_JI_GC_BRIDGE_TYPES; static const uint32_t NUM_GC_BRIDGE_TYPES; public: @@ -73,10 +76,15 @@ namespace xamarin::android::internal const MonoJavaGCBridgeType& get_java_gc_bridge_type (uint32_t index) { - if (index >= NUM_GC_BRIDGE_TYPES) - return empty_bridge_type; // Not ideal... + if (index < NUM_XA_GC_BRIDGE_TYPES) + return mono_xa_gc_bridge_types [index]; + + index -= NUM_XA_GC_BRIDGE_TYPES; + if (index < NUM_JI_GC_BRIDGE_TYPES) + return mono_ji_gc_bridge_types [index]; - return mono_java_gc_bridge_types [index]; + index -= NUM_JI_GC_BRIDGE_TYPES; + return empty_bridge_type; // Not ideal... } MonoJavaGCBridgeInfo& get_java_gc_bridge_info (uint32_t index) diff --git a/tests/Runtime-AppBundle/Mono.Android-TestsAppBundle.csproj b/tests/Runtime-AppBundle/Mono.Android-TestsAppBundle.csproj index 3122174af9b..793fa8b7d00 100644 --- a/tests/Runtime-AppBundle/Mono.Android-TestsAppBundle.csproj +++ b/tests/Runtime-AppBundle/Mono.Android-TestsAppBundle.csproj @@ -46,6 +46,7 @@ 4 false true + r8 @@ -64,6 +65,9 @@ + + + Resources\drawable\Icon.png @@ -118,6 +122,10 @@ {CB2335CB-0050-4020-8A05-E9614EDAA05E} TestRunner.NUnit + + {6CB00820-A66B-43E5-8785-ED456C6E9F39} + Java.Interop-Tests + {8CB5FF58-FF95-43B9-9064-9ACE9525866F} Mono.Android-Test.Library diff --git a/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj b/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj index 560945d0980..39810986789 100644 --- a/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj +++ b/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj @@ -45,6 +45,7 @@ 4 false true + r8 @@ -63,6 +64,9 @@ + + + @@ -97,6 +101,10 @@ {CB2335CB-0050-4020-8A05-E9614EDAA05E} TestRunner.NUnit + + {6CB00820-A66B-43E5-8785-ED456C6E9F39} + Java.Interop-Tests + {8CB5FF58-FF95-43B9-9064-9ACE9525866F} Mono.Android-Test.Library diff --git a/tests/TestRunner.Core/TestRunner.Core.csproj b/tests/TestRunner.Core/TestRunner.Core.csproj index b7627a66dae..6e0b2cdb5a5 100644 --- a/tests/TestRunner.Core/TestRunner.Core.csproj +++ b/tests/TestRunner.Core/TestRunner.Core.csproj @@ -8,15 +8,15 @@ Library Xamarin.Android.UnitTests TestRunner.Core - v8.1 + v9.0 Resources\Resource.designer.cs Resource Resources Assets - true + $(AndroidFrameworkVersion) true false bin\Debug @@ -26,6 +26,7 @@ None + $(AndroidFrameworkVersion) true true bin\Release diff --git a/tests/TestRunner.NUnit/TestRunner.NUnit.csproj b/tests/TestRunner.NUnit/TestRunner.NUnit.csproj index 9a162b00004..e85d234a57d 100644 --- a/tests/TestRunner.NUnit/TestRunner.NUnit.csproj +++ b/tests/TestRunner.NUnit/TestRunner.NUnit.csproj @@ -8,15 +8,15 @@ Library Xamarin.Android.UnitTests.NUnit TestRunner.NUnit - v8.1 + v9.0 Resources\Resource.designer.cs Resource Resources Assets - true + $(AndroidFrameworkVersion) true false bin\Debug @@ -26,6 +26,7 @@ None + $(AndroidFrameworkVersion) true true bin\Release