diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs index f81c0b0b87e..075ed4f9c41 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs @@ -773,7 +773,7 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index, JniSignature = registerInfo.Signature, Connector = registerInfo.Connector, ManagedMethodName = methodName, - NativeCallbackName = isConstructor ? "n_ctor" : $"n_{methodName}", + NativeCallbackName = GetNativeCallbackName (registerInfo.Connector, methodName, isConstructor), IsConstructor = isConstructor, DeclaringTypeName = result.Value.DeclaringTypeName, DeclaringAssemblyName = result.Value.DeclaringAssemblyName, @@ -818,7 +818,7 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index, JniSignature = propRegister.Signature, Connector = propRegister.Connector, ManagedMethodName = getterName, - NativeCallbackName = $"n_{getterName}", + NativeCallbackName = GetNativeCallbackName (propRegister.Connector, getterName, false), IsConstructor = false, DeclaringTypeName = baseTypeName, DeclaringAssemblyName = baseAssemblyName, @@ -866,12 +866,18 @@ static void AddMarshalMethod (List methods, RegisterInfo regi string managedName = index.Reader.GetString (methodDef.Name); string jniSignature = registerInfo.Signature ?? "()V"; + string declaringTypeName = ""; + string declaringAssemblyName = ""; + ParseConnectorDeclaringType (registerInfo.Connector, out declaringTypeName, out declaringAssemblyName); + methods.Add (new MarshalMethodInfo { JniName = registerInfo.JniName, JniSignature = jniSignature, Connector = registerInfo.Connector, ManagedMethodName = managedName, - NativeCallbackName = isConstructor ? "n_ctor" : $"n_{managedName}", + DeclaringTypeName = declaringTypeName, + DeclaringAssemblyName = declaringAssemblyName, + NativeCallbackName = GetNativeCallbackName (registerInfo.Connector, managedName, isConstructor), IsConstructor = isConstructor, IsExport = isExport, IsInterfaceImplementation = isInterfaceImplementation, @@ -1383,6 +1389,71 @@ bool ExtendsJavaPeer (TypeDefinition typeDef, AssemblyIndex index) return (typeName, parentJniName, ns); } + /// + /// Derives the native callback method name from a [Register] attribute's Connector field. + /// The Connector may be a simple name like "GetOnCreate_Landroid_os_Bundle_Handler" + /// or a qualified name like "GetOnClick_Landroid_view_View_Handler:Android.Views.View/IOnClickListenerInvoker, Mono.Android, …". + /// In both cases the result is e.g. "n_OnCreate_Landroid_os_Bundle_". + /// Falls back to "n_{managedName}" when the Connector doesn't follow the expected pattern. + /// + static string GetNativeCallbackName (string? connector, string managedName, bool isConstructor) + { + if (isConstructor) { + return "n_ctor"; + } + + if (connector is not null) { + // Strip the optional type qualifier after ':' + int colonIndex = connector.IndexOf (':'); + string handlerName = colonIndex >= 0 ? connector.Substring (0, colonIndex) : connector; + + if (handlerName.StartsWith ("Get", StringComparison.Ordinal) + && handlerName.EndsWith ("Handler", StringComparison.Ordinal)) { + return "n_" + handlerName.Substring (3, handlerName.Length - 3 - "Handler".Length); + } + } + + return $"n_{managedName}"; + } + + /// + /// Parses the type qualifier from a Connector string. + /// Connector format is either assembly-qualified: + /// "GetOnClickHandler:Android.Views.View/IOnClickListenerInvoker, Mono.Android, Version=…" + /// or type-only: "GetOnClickHandler:Android.Views.IOnClickListenerInvoker". + /// Extracts the managed type name (converting /+ for nested types) and assembly name (if present). + /// + static void ParseConnectorDeclaringType (string? connector, out string declaringTypeName, out string declaringAssemblyName) + { + declaringTypeName = ""; + declaringAssemblyName = ""; + + if (connector is null) { + return; + } + + int colonIndex = connector.IndexOf (':'); + if (colonIndex < 0) { + return; + } + + // After ':' is typically "TypeName, AssemblyName, Version=…" (assembly-qualified name), + // but some connectors only provide "TypeName" without an assembly. + string typeQualified = connector.Substring (colonIndex + 1); + int commaIndex = typeQualified.IndexOf (','); + + if (commaIndex < 0) { + // No assembly information; treat the whole segment as the type name + declaringTypeName = typeQualified.Trim ().Replace ('/', '+'); + return; + } + + declaringTypeName = typeQualified.Substring (0, commaIndex).Trim ().Replace ('/', '+'); + string rest = typeQualified.Substring (commaIndex + 1).Trim (); + int nextComma = rest.IndexOf (','); + declaringAssemblyName = nextComma >= 0 ? rest.Substring (0, nextComma).Trim () : rest.Trim (); + } + static string GetCrc64PackageName (string ns, string assemblyName) { // Only Mono.Android preserves the namespace directly diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/JcwJavaSourceGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/JcwJavaSourceGeneratorTests.cs index 18c6ff7d6b9..78fcb9159e6 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/JcwJavaSourceGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/JcwJavaSourceGeneratorTests.cs @@ -245,8 +245,8 @@ public void Generate_MarshalMethod_HasOverrideAndNativeDeclaration () var java = GenerateFixture ("my/app/MainActivity"); AssertContainsLine ("@Override\n", java); AssertContainsLine ("public void onCreate (android.os.Bundle p0)\n", java); - AssertContainsLine ("n_OnCreate (p0);\n", java); - AssertContainsLine ("public native void n_OnCreate (android.os.Bundle p0);\n", java); + AssertContainsLine ("n_OnCreate_Landroid_os_Bundle_ (p0);\n", java); + AssertContainsLine ("public native void n_OnCreate_Landroid_os_Bundle_ (android.os.Bundle p0);\n", java); } } diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/InterfaceMethodDetectionTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/InterfaceMethodDetectionTests.cs index 9d78510275c..fe3aec846d9 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/InterfaceMethodDetectionTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/InterfaceMethodDetectionTests.cs @@ -18,6 +18,7 @@ public void ImplicitInterfaceImpl_DetectsOnClickWithCorrectSignatureAndConnector var onClick = peer.MarshalMethods.First (m => m.JniName == "onClick"); Assert.Equal ("(Landroid/view/View;)V", onClick.JniSignature); Assert.Equal ("GetOnClick_Landroid_view_View_Handler:Android.Views.IOnClickListenerInvoker", onClick.Connector); + Assert.Equal ("Android.Views.IOnClickListenerInvoker", onClick.DeclaringTypeName); } [Fact] @@ -53,7 +54,9 @@ public void InterfacePropertyImpl_DetectedWithCorrectSignature () var peer = FindFixtureByJavaName ("my/app/ImplicitPropertyImpl"); var getName = peer.MarshalMethods.First (m => m.JniName == "getName"); Assert.Equal ("()Ljava/lang/String;", getName.JniSignature); - Assert.Equal ("GetGetNameHandler:Android.Views.IHasNameInvoker", getName.Connector); + Assert.Equal ("GetGetNameHandler:Android.Views.IHasNameInvoker, TestFixtures, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", getName.Connector); + Assert.Equal ("Android.Views.IHasNameInvoker", getName.DeclaringTypeName); + Assert.Equal ("TestFixtures", getName.DeclaringAssemblyName); } [Fact] diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/OverrideDetectionTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/OverrideDetectionTests.cs index 07e005e6482..54f7e0e133a 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/OverrideDetectionTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/OverrideDetectionTests.cs @@ -15,7 +15,7 @@ public void Override_DetectedWithCorrectRegistration () var peer = FindFixtureByJavaName ("my/app/UserActivity"); var onCreate = peer.MarshalMethods.First (m => m.JniName == "onCreate"); Assert.Equal ("(Landroid/os/Bundle;)V", onCreate.JniSignature); - Assert.Equal ("n_OnCreate", onCreate.NativeCallbackName); + Assert.Equal ("n_OnCreate_Landroid_os_Bundle_", onCreate.NativeCallbackName); Assert.False (onCreate.IsConstructor); Assert.Equal ("GetOnCreate_Landroid_os_Bundle_Handler", onCreate.Connector); Assert.NotNull (peer.ActivationCtor); diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs index 196a5261510..a15b12ef758 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs @@ -104,7 +104,7 @@ public interface IOnLongClickListener [Register ("android/view/View$IHasName", "", "Android.Views.IHasNameInvoker")] public interface IHasName { - [Register ("getName", "()Ljava/lang/String;", "GetGetNameHandler:Android.Views.IHasNameInvoker")] + [Register ("getName", "()Ljava/lang/String;", "GetGetNameHandler:Android.Views.IHasNameInvoker, TestFixtures, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")] string? Name { get; } }