From 8c8cc97968446a5f995d60d5bd3dd38a7b12b857 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 23 Jul 2019 14:45:29 -0400 Subject: [PATCH] [Mono.Android-Tests] Run Java.Interop tests as well. Context: https://github.com/xamarin/java.interop/pull/454 Context: https://github.com/xamarin/monodroid/commit/e318861e Context: https://github.com/xamarin/xamarin-android/commit/7d32ef3e6 Context: https://github.com/xamarin/xamarin-android/commit/1a2eb953af76b76b2443b744894523ddfe239ecf When use of `Java.Interop.dll` was originally added in xamarin/monodroid@e318861e, it didn't implement the "full" `JniRuntime` abstraction, which meant that until xamarin/xamarin-android@1a2eb953, trying to instantiate a `JavaInt32Array` instance would result in a `NotImplementedException`, because `Android.Runtime.AndroidValueManager.AddPeer()` threw a `NotImplementedException`. Commit xamarin/xamarin-android@1a2eb953 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` 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()` 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. 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 | 24 ++ build-tools/scripts/Jar.targets | 6 +- .../Android.Runtime/AndroidRuntime.cs | 351 +++++++++++++++++- .../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 | 236 +----------- src/Mono.Android/Java.Lang/Throwable.cs | 25 +- .../Test/Java.Interop-Tests/.gitignore | 1 + .../Java.Interop-Tests.csproj | 86 +++++ .../Java.Interop-Tests.targets | 24 ++ .../JavaInterop_Tests_Reference.cs | 9 + .../Properties/AssemblyInfo.cs | 30 ++ .../Resources/AboutResources.txt | 44 +++ .../Test/Mono.Android-Tests.csproj | 10 +- .../NUnitInstrumentation.cs | 5 +- .../TestInstrumentation.cs | 1 + src/monodroid/jni/monodroid-glue.cc | 11 +- src/monodroid/jni/osbridge.cc | 12 +- src/monodroid/jni/osbridge.hh | 16 +- .../Mono.Android-TestsMultiDex.csproj | 18 +- tests/TestRunner.Core/TestRunner.Core.csproj | 5 +- .../TestRunner.NUnit/TestRunner.NUnit.csproj | 5 +- 32 files changed, 699 insertions(+), 308 deletions(-) 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 diff --git a/Xamarin.Android-Tests.sln b/Xamarin.Android-Tests.sln index 0d6de66b5dc..ee7d94b987e 100644 --- a/Xamarin.Android-Tests.sln +++ b/Xamarin.Android-Tests.sln @@ -90,6 +90,14 @@ 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("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Interop-Tests", "external\Java.Interop\src\Java.Interop\Tests\Interop-Tests.shproj", "{0ADB8D72-7479-49AF-8809-E03AE4A4EAE2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Linq.Expressions", "external\Java.Interop\lib\mono.linq.expressions\Mono.Linq.Expressions.csproj", "{0C001D50-4176-45AE-BDC8-BA626508B0CC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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 Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Mono.Android\Test\Mono.Android-Test.Shared.projitems*{0ab4956e-6fb9-4da0-9d49-ab65a3ff403a}*SharedItemsImports = 13 @@ -249,6 +257,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 + {0C001D50-4176-45AE-BDC8-BA626508B0CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C001D50-4176-45AE-BDC8-BA626508B0CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C001D50-4176-45AE-BDC8-BA626508B0CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C001D50-4176-45AE-BDC8-BA626508B0CC}.Release|Any CPU.Build.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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -284,6 +304,10 @@ Global {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} + {0ADB8D72-7479-49AF-8809-E03AE4A4EAE2} = {EFBC4DC0-DBFF-4DAA-B0B8-6D0CB02A25F5} + {0C001D50-4176-45AE-BDC8-BA626508B0CC} = {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} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8643CD20-B195-4919-8135-27549488237E} diff --git a/build-tools/scripts/Jar.targets b/build-tools/scripts/Jar.targets index 327013349b9..c58761be90f 100644 --- a/build-tools/scripts/Jar.targets +++ b/build-tools/scripts/Jar.targets @@ -25,10 +25,12 @@ <_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" + <_JIJar>$([System.IO.Path]::GetFullPath ('$(XAInstallPrefix)'))\xbuild\Xamarin\Android\java-interop.jar + <_CP>-cp "$(_JIJar)" - + (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,12 @@ protected override IEnumerable GetTypesForSimpleReference (string jniSimpl protected override IEnumerable GetSimpleReferences (Type type) { - var j = JNIEnv.GetJniName (type); - if (j == null) + var j = JNIEnv.monodroid_typemap_managed_to_java (type.FullName + ", " + type.Assembly.GetName ().Name); + if (j == IntPtr.Zero) return base.GetSimpleReferences (type); + var s = Marshal.PtrToStringAnsi (j); return base.GetSimpleReferences (type) - .Concat (Enumerable.Repeat (j, 1)); + .Concat (new [] { s }); } delegate Delegate GetCallbackHandler (); @@ -318,17 +328,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 +365,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 40241fab78a..813eed6f9cf 100644 --- a/src/Mono.Android/Android.Runtime/InputStreamInvoker.cs +++ b/src/Mono.Android/Android.Runtime/InputStreamInvoker.cs @@ -65,10 +65,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 b17086da4c9..0603a1480f2 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -57,6 +57,8 @@ public static partial class JNIEnv { static AndroidRuntime androidRuntime; + internal static AndroidValueManager AndroidValueManager; + #if !JAVA_INTEROP static JNIInvokeInterface invoke_iface; @@ -195,6 +197,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) #if JAVA_INTEROP androidRuntime = new AndroidRuntime (args->env, args->javaVm, androidSdkVersion > 10, args->grefLoader, args->Loader_loadClass); + AndroidValueManager = (AndroidValueManager) androidRuntime.ValueManager; #endif // JAVA_INTEROP if (Logger.LogTiming) { diff --git a/src/Mono.Android/Android.Runtime/JavaCollection.cs b/src/Mono.Android/Android.Runtime/JavaCollection.cs index a1dd231ef91..0f7eb6e04bc 100644 --- a/src/Mono.Android/Android.Runtime/JavaCollection.cs +++ b/src/Mono.Android/Android.Runtime/JavaCollection.cs @@ -118,7 +118,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 @@ -243,7 +243,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 4d433d86eb0..dd5e6ae9603 100644 --- a/src/Mono.Android/Android.Runtime/JavaDictionary.cs +++ b/src/Mono.Android/Android.Runtime/JavaDictionary.cs @@ -226,7 +226,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 @@ -404,7 +404,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 9eae03fdc64..9cc35810f87 100644 --- a/src/Mono.Android/Android.Runtime/JavaList.cs +++ b/src/Mono.Android/Android.Runtime/JavaList.cs @@ -229,7 +229,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 @@ -550,7 +550,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 b4fa8fb9628..4c728337af8 100644 --- a/src/Mono.Android/Android.Runtime/JavaSet.cs +++ b/src/Mono.Android/Android.Runtime/JavaSet.cs @@ -146,7 +146,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 @@ -261,7 +261,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 11d9415724b..5f7ea672a81 100644 --- a/src/Mono.Android/Android.Runtime/OutputStreamInvoker.cs +++ b/src/Mono.Android/Android.Runtime/OutputStreamInvoker.cs @@ -72,10 +72,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 7cfa945db63..1c5b5bb96fe 100644 --- a/src/Mono.Android/Java.Interop/Runtime.cs +++ b/src/Mono.Android/Java.Interop/Runtime.cs @@ -10,9 +10,16 @@ public static class Runtime { internal static IntPtr grefIGCUserPeer_class; + [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")] diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index 49d4108eb13..e75cbe540d0 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -231,12 +231,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); @@ -279,13 +279,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); @@ -297,18 +297,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..142b6cf1383 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,12 @@ 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); - } + JniEnvironment.Runtime.ValueManager.FinalizePeer (this); } public Object (IntPtr handle, JniHandleOwnership transfer) @@ -163,17 +151,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 +188,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 +205,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 +228,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 +268,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 +540,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..8554c637764 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,8 @@ 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); - } + JniEnvironment.Runtime.ValueManager.FinalizePeer (this); } #if JAVA_INTEROP @@ -288,17 +278,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 +314,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..5009aa123f1 --- /dev/null +++ b/src/Mono.Android/Test/Java.Interop-Tests/Java.Interop-Tests.csproj @@ -0,0 +1,86 @@ + + + + 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 + v9.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 + + + + + + + + + + + + + + + + + + + + + + {D1243BAB-23CA-4566-A2A3-3ADA2C2DC3AF} + Java.Interop.GenericMarshaler + + + {0C001D50-4176-45AE-BDC8-BA626508B0CC} + Mono.Linq.Expressions + + + + + + + + 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..e4804b6d206 --- /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/Mono.Android-Tests.csproj b/src/Mono.Android/Test/Mono.Android-Tests.csproj index 4364a8d886c..88e9976dfc0 100644 --- a/src/Mono.Android/Test/Mono.Android-Tests.csproj +++ b/src/Mono.Android/Test/Mono.Android-Tests.csproj @@ -87,17 +87,23 @@ {CB2335CB-0050-4020-8A05-E9614EDAA05E} TestRunner.NUnit + + {6CB00820-A66B-43E5-8785-ED456C6E9F39} + Java.Interop-Tests + {8CB5FF58-FF95-43B9-9064-9ACE9525866F} Mono.Android-Test.Library - + + + Xamarin.Android.Build.Tasks {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7} False False - + Xamarin.Android.NUnitLite {4D603AA3-3BFD-43C8-8050-0CD6C2601126} False 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/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 284b0bab96f..d360f6aa215 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -1188,7 +1188,9 @@ init_android_runtime (MonoDomain *domain, JNIEnv *env, jclass runtimeClass, jobj assm = utils.monodroid_load_assembly (domain, "Mono.Android"); 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)); } @@ -1200,6 +1202,13 @@ init_android_runtime (MonoDomain *domain, JNIEnv *env, jclass runtimeClass, jobj 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 bf6ca1e9534..96e5d0dc352 100644 --- a/src/monodroid/jni/osbridge.cc +++ b/src/monodroid/jni/osbridge.cc @@ -29,17 +29,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-MultiDex/Mono.Android-TestsMultiDex.csproj b/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj index 43a7707417d..5494960b455 100644 --- a/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj +++ b/tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj @@ -97,22 +97,28 @@ {CB2335CB-0050-4020-8A05-E9614EDAA05E} TestRunner.NUnit - + + {6CB00820-A66B-43E5-8785-ED456C6E9F39} + Java.Interop-Tests + + + {8CB5FF58-FF95-43B9-9064-9ACE9525866F} + Mono.Android-Test.Library + + + + Xamarin.Android.Build.Tasks {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7} False False - + Xamarin.Android.NUnitLite {4D603AA3-3BFD-43C8-8050-0CD6C2601126} False False - - {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