diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs
new file mode 100644
index 00000000000..5c712776bd6
--- /dev/null
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Android.Sdk.TrimmableTypeMap;
+
+///
+/// Helpers for parsing JNI method signatures.
+///
+static class JniSignatureHelper
+{
+ ///
+ /// Parses the JNI parameter type descriptors from a JNI method signature
+ /// and returns them as records.
+ ///
+ public static List ParseParameterTypes (string jniSignature)
+ {
+ var result = new List ();
+ int i = 1; // skip opening '('
+ while (i < jniSignature.Length && jniSignature [i] != ')') {
+ int start = i;
+ SkipSingleType (jniSignature, ref i);
+ result.Add (new JniParameterInfo { JniType = jniSignature.Substring (start, i - start) });
+ }
+ return result;
+ }
+
+ ///
+ /// Extracts the return type descriptor from a JNI method signature.
+ ///
+ public static string ParseReturnTypeString (string jniSignature)
+ {
+ int i = jniSignature.IndexOf (')') + 1;
+ return jniSignature.Substring (i);
+ }
+
+ static void SkipSingleType (string sig, ref int i)
+ {
+ switch (sig [i]) {
+ case 'V': case 'Z': case 'B': case 'C': case 'S':
+ case 'I': case 'J': case 'F': case 'D':
+ i++;
+ break;
+ case 'L':
+ int end = sig.IndexOf (';', i);
+ if (end < 0) {
+ throw new ArgumentException ($"Malformed JNI signature: missing ';' after 'L' at index {i} in '{sig}'");
+ }
+ i = end + 1;
+ break;
+ case '[':
+ i++;
+ SkipSingleType (sig, ref i);
+ break;
+ default:
+ throw new ArgumentException ($"Unknown JNI type character '{sig [i]}' in '{sig}' at index {i}");
+ }
+ }
+}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs
index ca5063ff019..e08676da6b4 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs
@@ -280,6 +280,15 @@ sealed record RegisterInfo
public bool DoNotGenerateAcw { get; init; }
}
+///
+/// Parsed [Export] attribute data for a method.
+///
+sealed record ExportInfo
+{
+ public IReadOnlyList? ThrownNames { get; init; }
+ public string? SuperArgumentsString { get; init; }
+}
+
class TypeAttributeInfo (string attributeName)
{
public string AttributeName { get; } = attributeName;
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
index f37c0dbb85a..97ab91393f6 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
namespace Microsoft.Android.Sdk.TrimmableTypeMap;
@@ -15,6 +16,13 @@ sealed record JavaPeerInfo
///
public required string JavaName { get; init; }
+ ///
+ /// Compat JNI type name, e.g., "myapp.namespace/MyType" for user types (uses raw namespace, not CRC64).
+ /// For MCW binding types (with [Register]), this equals .
+ /// Used by acw-map.txt to support legacy custom view name resolution in layout XMLs.
+ ///
+ public required string CompatJniName { get; init; }
+
///
/// Full managed type name, e.g., "Android.App.Activity".
///
@@ -50,6 +58,14 @@ sealed record JavaPeerInfo
///
public bool IsUnconditional { get; init; }
+ ///
+ /// Marshal methods: methods with [Register(name, sig, connector)], [Export], or
+ /// constructor registrations ([Register(".ctor", sig, "")] / [JniConstructorSignature]).
+ /// Constructors are identified by .
+ /// Ordered — the index in this list is the method's ordinal for RegisterNatives.
+ ///
+ public IReadOnlyList MarshalMethods { get; init; } = Array.Empty ();
+
///
/// Information about the activation constructor for this type.
/// May reference a base type's constructor if the type doesn't define its own.
@@ -79,6 +95,99 @@ sealed record JavaPeerInfo
public bool IsGenericDefinition { get; init; }
}
+///
+/// Describes a marshal method (a method with [Register] or [Export]) on a Java peer type.
+/// Contains all data needed to generate a UCO wrapper, a JCW native declaration,
+/// and a RegisterNatives call.
+///
+sealed record MarshalMethodInfo
+{
+ ///
+ /// JNI method name, e.g., "onCreate".
+ /// This is the Java method name (without n_ prefix).
+ ///
+ public required string JniName { get; init; }
+
+ ///
+ /// JNI method signature, e.g., "(Landroid/os/Bundle;)V".
+ /// Contains both parameter types and return type.
+ ///
+ public required string JniSignature { get; init; }
+
+ ///
+ /// The connector string from [Register], e.g., "GetOnCreate_Landroid_os_Bundle_Handler".
+ /// Null for [Export] methods.
+ ///
+ public string? Connector { get; init; }
+
+ ///
+ /// Name of the managed method this maps to, e.g., "OnCreate".
+ ///
+ public required string ManagedMethodName { get; init; }
+
+ ///
+ /// Full name of the type that declares the managed method (may be a base type).
+ /// Empty when the declaring type is the same as the peer type.
+ ///
+ public string DeclaringTypeName { get; init; } = "";
+
+ ///
+ /// Assembly name of the type that declares the managed method.
+ /// Needed for cross-assembly UCO wrapper generation.
+ /// Empty when the declaring type is the same as the peer type.
+ ///
+ public string DeclaringAssemblyName { get; init; } = "";
+
+ ///
+ /// The native callback method name, e.g., "n_onCreate".
+ /// This is the actual method the UCO wrapper delegates to.
+ ///
+ public required string NativeCallbackName { get; init; }
+
+ ///
+ /// JNI parameter types for UCO generation.
+ ///
+ public IReadOnlyList Parameters { get; init; } = Array.Empty ();
+
+ ///
+ /// JNI return type descriptor, e.g., "V", "Landroid/os/Bundle;".
+ ///
+ public required string JniReturnType { get; init; }
+
+ ///
+ /// True if this is a constructor registration.
+ ///
+ public bool IsConstructor { get; init; }
+
+ ///
+ /// For [Export] methods: Java exception types that the method declares it can throw.
+ /// Null for [Register] methods.
+ ///
+ public IReadOnlyList? ThrownNames { get; init; }
+
+ ///
+ /// For [Export] methods: super constructor arguments string.
+ /// Null for [Register] methods.
+ ///
+ public string? SuperArgumentsString { get; init; }
+}
+
+///
+/// Describes a JNI parameter for UCO method generation.
+///
+sealed record JniParameterInfo
+{
+ ///
+ /// JNI type descriptor, e.g., "Landroid/os/Bundle;", "I", "Z".
+ ///
+ public required string JniType { get; init; }
+
+ ///
+ /// Managed parameter type name, e.g., "Android.OS.Bundle", "System.Int32".
+ ///
+ public string ManagedType { get; init; } = "";
+}
+
///
/// Describes how to call the activation constructor for a Java peer type.
///
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
index 86cb2340a37..442f01229ce 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Reflection.Metadata;
@@ -164,6 +165,7 @@ void ScanAssembly (AssemblyIndex index, Dictionary results
// 3. Extends a known Java peer → auto-compute JNI name via CRC64
// 4. None of the above → not a Java peer, skip
string? jniName = null;
+ string? compatJniName = null;
bool doNotGenerateAcw = false;
index.RegisterInfoByType.TryGetValue (typeHandle, out var registerInfo);
@@ -171,15 +173,17 @@ void ScanAssembly (AssemblyIndex index, Dictionary results
if (registerInfo is not null && !string.IsNullOrEmpty (registerInfo.JniName)) {
jniName = registerInfo.JniName;
+ compatJniName = jniName;
doNotGenerateAcw = registerInfo.DoNotGenerateAcw;
} else if (attrInfo?.JniName is not null) {
// User type with [Activity(Name = "...")] but no [Register]
jniName = attrInfo.JniName;
+ compatJniName = jniName;
} else {
// No explicit JNI name — check if this type extends a known Java peer.
// If so, auto-compute JNI name from the managed type name via CRC64.
if (ExtendsJavaPeer (typeDef, index)) {
- jniName = ComputeAutoJniName (typeDef, index);
+ (jniName, compatJniName) = ComputeAutoJniNames (typeDef, index);
} else {
continue;
}
@@ -194,6 +198,9 @@ void ScanAssembly (AssemblyIndex index, Dictionary results
var isUnconditional = attrInfo is not null;
string? invokerTypeName = null;
+ // Collect marshal methods (including constructors) in a single pass over methods
+ var marshalMethods = CollectMarshalMethods (typeDef, index);
+
// Resolve activation constructor
var activationCtor = ResolveActivationCtor (fullName, typeDef, index);
@@ -214,6 +221,7 @@ void ScanAssembly (AssemblyIndex index, Dictionary results
var peer = new JavaPeerInfo {
JavaName = jniName,
+ CompatJniName = compatJniName,
ManagedTypeName = fullName,
ManagedTypeNamespace = ExtractNamespace (fullName),
ManagedTypeShortName = ExtractShortName (fullName),
@@ -222,6 +230,7 @@ void ScanAssembly (AssemblyIndex index, Dictionary results
IsAbstract = isAbstract,
DoNotGenerateAcw = doNotGenerateAcw,
IsUnconditional = isUnconditional,
+ MarshalMethods = marshalMethods,
ActivationCtor = activationCtor,
InvokerTypeName = invokerTypeName,
IsGenericDefinition = isGenericDefinition,
@@ -233,6 +242,186 @@ void ScanAssembly (AssemblyIndex index, Dictionary results
}
}
+ List CollectMarshalMethods (TypeDefinition typeDef, AssemblyIndex index)
+ {
+ var methods = new List ();
+
+ // Single pass over methods: collect marshal methods (including constructors)
+ foreach (var methodHandle in typeDef.GetMethods ()) {
+ var methodDef = index.Reader.GetMethodDefinition (methodHandle);
+ if (!TryGetMethodRegisterInfo (methodDef, index, out var registerInfo, out var exportInfo) || registerInfo is null) {
+ continue;
+ }
+
+ AddMarshalMethod (methods, registerInfo, methodDef, index, exportInfo);
+ }
+
+ // Collect [Register] from properties (attribute is on the property, not the getter)
+ foreach (var propHandle in typeDef.GetProperties ()) {
+ var propDef = index.Reader.GetPropertyDefinition (propHandle);
+ var propRegister = TryGetPropertyRegisterInfo (propDef, index);
+ if (propRegister is null) {
+ continue;
+ }
+
+ var accessors = propDef.GetAccessors ();
+ if (!accessors.Getter.IsNil) {
+ var getterDef = index.Reader.GetMethodDefinition (accessors.Getter);
+ AddMarshalMethod (methods, propRegister, getterDef, index);
+ }
+ }
+
+ return methods;
+ }
+
+ static void AddMarshalMethod (List methods, RegisterInfo registerInfo, MethodDefinition methodDef, AssemblyIndex index, ExportInfo? exportInfo = null)
+ {
+ // Skip methods that are just the JNI name (type-level [Register])
+ if (registerInfo.Signature is null && registerInfo.Connector is null) {
+ return;
+ }
+
+ bool isConstructor = registerInfo.JniName == "" || registerInfo.JniName == ".ctor";
+ string nativeCallbackName = $"n_{index.Reader.GetString (methodDef.Name)}";
+ if (isConstructor) {
+ int ctorIndex = 0;
+ foreach (var method in methods) {
+ if (method.IsConstructor) {
+ ctorIndex++;
+ }
+ }
+ nativeCallbackName = ctorIndex == 0 ? "n_ctor" : $"n_ctor_{ctorIndex}";
+ }
+
+ methods.Add (new MarshalMethodInfo {
+ JniName = registerInfo.JniName,
+ JniSignature = registerInfo.Signature ?? "()V",
+ Connector = registerInfo.Connector,
+ ManagedMethodName = index.Reader.GetString (methodDef.Name),
+ NativeCallbackName = nativeCallbackName,
+ JniReturnType = JniSignatureHelper.ParseReturnTypeString (registerInfo.Signature ?? "()V"),
+ Parameters = JniSignatureHelper.ParseParameterTypes (registerInfo.Signature ?? "()V"),
+ IsConstructor = isConstructor,
+ ThrownNames = exportInfo?.ThrownNames,
+ SuperArgumentsString = exportInfo?.SuperArgumentsString,
+ });
+ }
+
+ static bool TryGetMethodRegisterInfo (MethodDefinition methodDef, AssemblyIndex index, out RegisterInfo? registerInfo, out ExportInfo? exportInfo)
+ {
+ exportInfo = null;
+ foreach (var caHandle in methodDef.GetCustomAttributes ()) {
+ var ca = index.Reader.GetCustomAttribute (caHandle);
+ var attrName = AssemblyIndex.GetCustomAttributeName (ca, index.Reader);
+
+ if (attrName == "RegisterAttribute") {
+ registerInfo = index.ParseRegisterAttribute (ca);
+ return true;
+ }
+
+ if (attrName == "ExportAttribute") {
+ (registerInfo, exportInfo) = ParseExportAttribute (ca, methodDef, index);
+ return true;
+ }
+ }
+ registerInfo = null;
+ return false;
+ }
+
+ static RegisterInfo? TryGetPropertyRegisterInfo (PropertyDefinition propDef, AssemblyIndex index)
+ {
+ foreach (var caHandle in propDef.GetCustomAttributes ()) {
+ var ca = index.Reader.GetCustomAttribute (caHandle);
+ var attrName = AssemblyIndex.GetCustomAttributeName (ca, index.Reader);
+
+ if (attrName == "RegisterAttribute") {
+ return index.ParseRegisterAttribute (ca);
+ }
+ }
+ return null;
+ }
+
+ static (RegisterInfo registerInfo, ExportInfo exportInfo) ParseExportAttribute (CustomAttribute ca, MethodDefinition methodDef, AssemblyIndex index)
+ {
+ var value = index.DecodeAttribute (ca);
+
+ // [Export("name")] or [Export] (uses method name)
+ string? exportName = null;
+ if (value.FixedArguments.Length > 0) {
+ exportName = (string?)value.FixedArguments [0].Value;
+ }
+
+ List? thrownNames = null;
+ string? superArguments = null;
+
+ // Check Named arguments
+ foreach (var named in value.NamedArguments) {
+ if (named.Name == "Name" && named.Value is string name) {
+ exportName = name;
+ } else if (named.Name == "ThrownNames" && named.Value is ImmutableArray> names) {
+ thrownNames = new List (names.Length);
+ foreach (var item in names) {
+ if (item.Value is string s) {
+ thrownNames.Add (s);
+ }
+ }
+ } else if (named.Name == "SuperArgumentsString" && named.Value is string superArgs) {
+ superArguments = superArgs;
+ }
+ }
+
+ if (string.IsNullOrEmpty (exportName)) {
+ exportName = index.Reader.GetString (methodDef.Name);
+ }
+ string resolvedExportName = exportName ?? throw new InvalidOperationException ("Export name should not be null at this point.");
+
+ // Build JNI signature from method signature
+ var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default);
+ var jniSig = BuildJniSignatureFromManaged (sig);
+
+ return (
+ new RegisterInfo { JniName = resolvedExportName, Signature = jniSig, Connector = null, DoNotGenerateAcw = false },
+ new ExportInfo { ThrownNames = thrownNames, SuperArgumentsString = superArguments }
+ );
+ }
+
+ static string BuildJniSignatureFromManaged (MethodSignature sig)
+ {
+ var sb = new System.Text.StringBuilder ();
+ sb.Append ('(');
+ foreach (var param in sig.ParameterTypes) {
+ sb.Append (ManagedTypeToJniDescriptor (param));
+ }
+ sb.Append (')');
+ sb.Append (ManagedTypeToJniDescriptor (sig.ReturnType));
+ return sb.ToString ();
+ }
+
+ static string ManagedTypeToJniDescriptor (string managedType)
+ {
+ switch (managedType) {
+ case "System.Void": return "V";
+ case "System.Boolean": return "Z";
+ case "System.Byte":
+ case "System.SByte": return "B";
+ case "System.Char": return "C";
+ case "System.Int16":
+ case "System.UInt16": return "S";
+ case "System.Int32":
+ case "System.UInt32": return "I";
+ case "System.Int64":
+ case "System.UInt64": return "J";
+ case "System.Single": return "F";
+ case "System.Double": return "D";
+ case "System.String": return "Ljava/lang/String;";
+ default:
+ if (managedType.EndsWith ("[]")) {
+ return $"[{ManagedTypeToJniDescriptor (managedType.Substring (0, managedType.Length - 2))}";
+ }
+ return "Ljava/lang/Object;";
+ }
+ }
+
ActivationCtorInfo? ResolveActivationCtor (string typeName, TypeDefinition typeDef, AssemblyIndex index)
{
var cacheKey = (typeName, index.AssemblyName);
@@ -453,21 +642,29 @@ bool ExtendsJavaPeer (TypeDefinition typeDef, AssemblyIndex index)
}
///
- /// Compute JNI name for a type without [Register] or component Name.
+ /// Compute both JNI name and compat JNI name for a type without [Register] or component Name.
/// JNI name uses CRC64 hash of "namespace:assemblyName" for the package.
- /// If a declaring type has [Register], its JNI name is used as prefix.
+ /// Compat JNI name uses the raw managed namespace (lowercased).
+ /// If a declaring type has [Register], its JNI name is used as prefix for both.
/// Generic backticks are replaced with _.
///
- static string ComputeAutoJniName (TypeDefinition typeDef, AssemblyIndex index)
+ static (string jniName, string compatJniName) ComputeAutoJniNames (TypeDefinition typeDef, AssemblyIndex index)
{
var (typeName, parentJniName, ns) = ComputeTypeNameParts (typeDef, index);
if (parentJniName is not null) {
- return $"{parentJniName}_{typeName}";
+ var name = $"{parentJniName}_{typeName}";
+ return (name, name);
}
var packageName = GetCrc64PackageName (ns, index.AssemblyName);
- return $"{packageName}/{typeName}";
+ var jniName = $"{packageName}/{typeName}";
+
+ string compatName = ns.Length == 0
+ ? typeName
+ : $"{ns.ToLowerInvariant ().Replace ('.', '/')}/{typeName}";
+
+ return (jniName, compatName);
}
///
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs
index 0a29a1b9231..4ffb44c7705 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs
@@ -57,6 +57,7 @@ private protected static JavaPeerInfo MakeMcwPeer (string jniName, string manage
var (ns, shortName) = ParseManagedTypeName (managedName);
return new JavaPeerInfo {
JavaName = jniName,
+ CompatJniName = jniName,
ManagedTypeName = managedName,
ManagedTypeNamespace = ns,
ManagedTypeShortName = shortName,
@@ -76,17 +77,32 @@ private protected static JavaPeerInfo MakePeerWithActivation (string jniName, st
}
private protected static JavaPeerInfo MakeAcwPeer (string jniName, string managedName, string asmName)
- => MakePeerWithActivation (jniName, managedName, asmName);
+ {
+ return MakePeerWithActivation (jniName, managedName, asmName) with {
+ DoNotGenerateAcw = false,
+ MarshalMethods = new List {
+ new MarshalMethodInfo {
+ JniName = "",
+ NativeCallbackName = "n_ctor",
+ JniSignature = "()V",
+ JniReturnType = "V",
+ ManagedMethodName = ".ctor",
+ IsConstructor = true,
+ },
+ },
+ };
+ }
private protected static JavaPeerInfo MakeInterfacePeer (
- string jniName,
- string managedName,
- string asmName,
- string invokerName)
+ string jniName = "android/view/View$OnClickListener",
+ string managedName = "Android.Views.View+IOnClickListener",
+ string asmName = "Mono.Android",
+ string invokerName = "Android.Views.View+IOnClickListenerInvoker")
{
var (ns, shortName) = ParseManagedTypeName (managedName);
return new JavaPeerInfo {
JavaName = jniName,
+ CompatJniName = jniName,
ManagedTypeName = managedName,
ManagedTypeNamespace = ns,
ManagedTypeShortName = shortName,
@@ -107,4 +123,16 @@ private protected static List GetMemberRefNames (MetadataReader reader)
.Select (i => reader.GetMemberReference (MetadataTokens.MemberReferenceHandle (i)))
.Select (m => reader.GetString (m.Name))
.ToList ();
+
+ private protected static MarshalMethodInfo MakeMarshalMethod (string jniName, string callbackName, string jniSig, bool isConstructor = false)
+ {
+ return new MarshalMethodInfo {
+ JniName = jniName,
+ NativeCallbackName = callbackName,
+ JniSignature = jniSig,
+ JniReturnType = JniSignatureHelper.ParseReturnTypeString (jniSig),
+ ManagedMethodName = jniName,
+ IsConstructor = isConstructor,
+ };
+ }
}
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs
index 9b2090fcf5c..18d427744ed 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs
@@ -106,6 +106,7 @@ public void Generate_DuplicateJniNames_CreatesAliasEntriesAndAssociationAttribut
var peers = new List {
new JavaPeerInfo {
JavaName = "test/Duplicate",
+ CompatJniName = "test/Duplicate",
ManagedTypeName = "Test.Duplicate1",
ManagedTypeNamespace = "Test",
ManagedTypeShortName = "Duplicate1",
@@ -118,6 +119,7 @@ public void Generate_DuplicateJniNames_CreatesAliasEntriesAndAssociationAttribut
},
new JavaPeerInfo {
JavaName = "test/Duplicate",
+ CompatJniName = "test/Duplicate",
ManagedTypeName = "Test.Duplicate2",
ManagedTypeNamespace = "Test",
ManagedTypeShortName = "Duplicate2",
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs
index 9308b6cc41d..74f27ac0a22 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs
@@ -183,7 +183,7 @@ public void Build_PeerWithActivation_CreatesNamedProxy (string jniName, string m
[Fact]
public void Build_PeerWithInvoker_CreatesProxy ()
{
- var peer = MakeInterfacePeer ("android/view/View$OnClickListener", "Android.Views.View+IOnClickListener", "Mono.Android", "Android.Views.View+IOnClickListenerInvoker");
+ var peer = MakeInterfacePeer ();
var model = BuildModel (new [] { peer });
Assert.Single (model.ProxyTypes);
@@ -434,7 +434,7 @@ public void Fixture_AcwType_HasProxy (string javaName, string expectedProxyName)
var model = BuildModel (new [] { peer }, "TypeMap");
- if (peer.ActivationCtor != null) {
+ if (peer.ActivationCtor != null && peer.MarshalMethods.Count > 0) {
var proxy = model.ProxyTypes.FirstOrDefault (p => p.TypeName == expectedProxyName);
Assert.NotNull (proxy);
}
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs
index 6e2795c303b..3ed0c175cf6 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs
@@ -1,9 +1,58 @@
+using System.Linq;
using Xunit;
namespace Microsoft.Android.Sdk.TrimmableTypeMap.Tests;
public partial class JavaPeerScannerTests
{
+ [Theory]
+ [InlineData ("android/app/Activity", "OnCreate", "onCreate", "(Landroid/os/Bundle;)V")]
+ [InlineData ("android/app/Activity", "OnStart", "onStart", "()V")]
+ [InlineData ("my/app/MainActivity", "OnCreate", "onCreate", "(Landroid/os/Bundle;)V")]
+ [InlineData ("my/app/AbstractBase", "DoWork", "doWork", "()V")]
+ [InlineData ("java/lang/Throwable", "Message", "getMessage", "()Ljava/lang/String;")]
+ [InlineData ("my/app/TouchHandler", "OnTouch", "onTouch", "(Landroid/view/View;I)Z")]
+ [InlineData ("my/app/TouchHandler", "OnFocusChange", "onFocusChange", "(Landroid/view/View;Z)V")]
+ [InlineData ("my/app/TouchHandler", "OnScroll", "onScroll", "(IFJD)V")]
+ [InlineData ("my/app/TouchHandler", "SetItems", "setItems", "([Ljava/lang/String;)V")]
+ public void Scan_MarshalMethod_HasCorrectSignature (string javaName, string managedName, string jniName, string jniSig)
+ {
+ var method = FindFixtureByJavaName (javaName)
+ .MarshalMethods.FirstOrDefault (m => m.ManagedMethodName == managedName || m.JniName == jniName);
+ Assert.NotNull (method);
+ Assert.Equal (jniName, method.JniName);
+ Assert.Equal (jniSig, method.JniSignature);
+ }
+
+ [Fact]
+ public void Scan_MarshalMethod_ConstructorsAndSpecialCases ()
+ {
+ var ctors = FindFixtureByJavaName ("my/app/CustomView")
+ .MarshalMethods.Where (m => m.IsConstructor).ToList ();
+ Assert.Equal (2, ctors.Count);
+ Assert.Equal ("()V", ctors [0].JniSignature);
+ Assert.Equal ("(Landroid/content/Context;)V", ctors [1].JniSignature);
+
+ Assert.DoesNotContain (FindFixtureByJavaName ("my/app/MyHelper").MarshalMethods, m => m.IsConstructor);
+
+ var exportMethod = FindFixtureByJavaName ("my/app/ExportExample").MarshalMethods.Single ();
+ Assert.Equal ("myExportedMethod", exportMethod.JniName);
+ Assert.Null (exportMethod.Connector);
+
+ var onStart = FindFixtureByJavaName ("android/app/Activity")
+ .MarshalMethods.FirstOrDefault (m => m.JniName == "onStart");
+ Assert.NotNull (onStart);
+ Assert.Equal ("", onStart.Connector);
+
+ var onClick = FindFixtureByManagedName ("Android.Views.IOnClickListener")
+ .MarshalMethods.FirstOrDefault (m => m.JniName == "onClick");
+ Assert.NotNull (onClick);
+ Assert.Equal ("(Landroid/view/View;)V", onClick.JniSignature);
+
+ Assert.Equal ("Android.Views.IOnClickListenerInvoker",
+ FindFixtureByManagedName ("Android.Views.IOnClickListener").InvokerTypeName);
+ }
+
[Theory]
[InlineData ("android/app/Activity", "Android.App.Activity")]
[InlineData ("my/app/SimpleActivity", "Android.App.Activity")]
@@ -40,6 +89,22 @@ public void Scan_MultipleInterfaces_AllResolved ()
Assert.Empty (FindFixtureByJavaName ("my/app/MyHelper").ImplementedInterfaceJavaNames);
}
+ [Theory]
+ [InlineData ("android/app/Activity", "android/app/Activity")]
+ [InlineData ("my/app/MainActivity", "my/app/MainActivity")]
+ public void Scan_CompatJniName (string javaName, string expectedCompat)
+ {
+ Assert.Equal (expectedCompat, FindFixtureByJavaName (javaName).CompatJniName);
+ }
+
+ [Fact]
+ public void Scan_CompatJniName_UnregisteredType_UsesRawNamespace ()
+ {
+ var unregistered = FindFixtureByManagedName ("MyApp.UnregisteredHelper");
+ Assert.StartsWith ("crc64", unregistered.JavaName);
+ Assert.Equal ("myapp/UnregisteredHelper", unregistered.CompatJniName);
+ }
+
[Fact]
public void Scan_CustomJniNameProviderAttribute_UsesNameFromAttribute ()
{
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.EdgeCases.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.EdgeCases.cs
index 916431945f5..1e7b0c29f16 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.EdgeCases.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.EdgeCases.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using Xunit;
namespace Microsoft.Android.Sdk.TrimmableTypeMap.Tests;
@@ -36,6 +37,8 @@ public void Scan_UnregisteredNestedType_UsesParentJniPrefix (string managedName,
public void Scan_EmptyNamespace_Handled ()
{
Assert.Equal ("GlobalType", FindFixtureByJavaName ("my/app/GlobalType").ManagedTypeName);
+ Assert.Equal ("GlobalUnregisteredType",
+ FindFixtureByManagedName ("GlobalUnregisteredType").CompatJniName);
}
[Theory]
@@ -47,4 +50,13 @@ public void Scan_UnregisteredType_DiscoveredWithCrc64Name (string managedName)
{
Assert.StartsWith ("crc64", FindFixtureByManagedName (managedName).JavaName);
}
+
+ [Fact]
+ public void Scan_ExportOnUnregisteredType_MethodDiscovered ()
+ {
+ var exportMethod = FindFixtureByManagedName ("MyApp.UnregisteredExporter")
+ .MarshalMethods.FirstOrDefault (m => m.JniName == "doExportedWork");
+ Assert.NotNull (exportMethod);
+ Assert.Null (exportMethod.Connector);
+ }
}