diff --git a/eng/Versions.props b/eng/Versions.props
index d87b6a2fe34..bf1e2b58feb 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -16,6 +16,7 @@
11.0.100-preview.1.26076.102
0.11.5-preview.26076.102
9.0.4
+ 11.0.0-preview.1.26104.118
36.1.30
$(MicrosoftNETSdkAndroidManifest100100PackageVersion)
diff --git a/src-ThirdParty/System.Runtime.CompilerServices/CompilerFeaturePolyfills.cs b/src-ThirdParty/System.Runtime.CompilerServices/CompilerFeaturePolyfills.cs
new file mode 100644
index 00000000000..c33ab5025c2
--- /dev/null
+++ b/src-ThirdParty/System.Runtime.CompilerServices/CompilerFeaturePolyfills.cs
@@ -0,0 +1,23 @@
+// Polyfills for C# language features on netstandard2.0
+
+// Required for init-only setters
+namespace System.Runtime.CompilerServices
+{
+ static class IsExternalInit { }
+
+ [AttributeUsage (AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
+ sealed class RequiredMemberAttribute : Attribute { }
+
+ [AttributeUsage (AttributeTargets.All, AllowMultiple = true, Inherited = false)]
+ sealed class CompilerFeatureRequiredAttribute (string featureName) : Attribute
+ {
+ public string FeatureName { get; } = featureName;
+ public bool IsOptional { get; init; }
+ }
+}
+
+namespace System.Diagnostics.CodeAnalysis
+{
+ [AttributeUsage (AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
+ sealed class SetsRequiredMembersAttribute : Attribute { }
+}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj
new file mode 100644
index 00000000000..d75d4d7df1e
--- /dev/null
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+ $(TargetFrameworkNETStandard)
+ enable
+ Nullable
+ Microsoft.Android.Sdk.TrimmableTypeMap
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs
new file mode 100644
index 00000000000..cf8bd977a71
--- /dev/null
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs
@@ -0,0 +1,262 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
+
+namespace Microsoft.Android.Sdk.TrimmableTypeMap;
+
+///
+/// Phase 1 index for a single assembly. Built in one pass over TypeDefinitions,
+/// all subsequent lookups are O(1) dictionary lookups.
+///
+sealed class AssemblyIndex : IDisposable
+{
+ readonly PEReader peReader;
+ readonly CustomAttributeTypeProvider customAttributeTypeProvider;
+
+ public MetadataReader Reader { get; }
+ public string AssemblyName { get; }
+ public string FilePath { get; }
+
+ ///
+ /// Maps full managed type name (e.g., "Android.App.Activity") to its TypeDefinitionHandle.
+ ///
+ public Dictionary TypesByFullName { get; } = new (StringComparer.Ordinal);
+
+ ///
+ /// Cached [Register] attribute data per type.
+ ///
+ public Dictionary RegisterInfoByType { get; } = new ();
+
+ ///
+ /// All custom attribute data per type, pre-parsed for the attributes we care about.
+ ///
+ public Dictionary AttributesByType { get; } = new ();
+
+ AssemblyIndex (PEReader peReader, MetadataReader reader, string assemblyName, string filePath)
+ {
+ this.peReader = peReader;
+ this.customAttributeTypeProvider = new CustomAttributeTypeProvider (reader);
+ Reader = reader;
+ AssemblyName = assemblyName;
+ FilePath = filePath;
+ }
+
+ public static AssemblyIndex Create (string filePath)
+ {
+ var peReader = new PEReader (File.OpenRead (filePath));
+ var reader = peReader.GetMetadataReader ();
+ var assemblyName = reader.GetString (reader.GetAssemblyDefinition ().Name);
+ var index = new AssemblyIndex (peReader, reader, assemblyName, filePath);
+ index.Build ();
+ return index;
+ }
+
+ void Build ()
+ {
+ foreach (var typeHandle in Reader.TypeDefinitions) {
+ var typeDef = Reader.GetTypeDefinition (typeHandle);
+
+ var fullName = MetadataTypeNameResolver.GetFullName (typeDef, Reader);
+ if (fullName.Length == 0) {
+ continue;
+ }
+
+ TypesByFullName [fullName] = typeHandle;
+
+ var (registerInfo, attrInfo) = ParseAttributes (typeDef);
+
+ if (attrInfo is not null) {
+ AttributesByType [typeHandle] = attrInfo;
+ }
+
+ if (registerInfo is not null) {
+ RegisterInfoByType [typeHandle] = registerInfo;
+ }
+ }
+ }
+
+ (RegisterInfo? register, TypeAttributeInfo? attrs) ParseAttributes (TypeDefinition typeDef)
+ {
+ RegisterInfo? registerInfo = null;
+ TypeAttributeInfo? attrInfo = null;
+
+ foreach (var caHandle in typeDef.GetCustomAttributes ()) {
+ var ca = Reader.GetCustomAttribute (caHandle);
+ var attrName = GetCustomAttributeName (ca, Reader);
+
+ if (attrName is null) {
+ continue;
+ }
+
+ if (attrName == "RegisterAttribute") {
+ registerInfo = ParseRegisterAttribute (ca);
+ } else if (attrName == "ExportAttribute") {
+ // [Export] is a method-level attribute; it is parsed at scan time by JavaPeerScanner
+ } else if (IsKnownComponentAttribute (attrName)) {
+ attrInfo ??= CreateTypeAttributeInfo (attrName);
+ var name = TryGetNameProperty (ca);
+ if (name is not null) {
+ attrInfo.JniName = name.Replace ('.', '/');
+ }
+ if (attrInfo is ApplicationAttributeInfo applicationAttributeInfo) {
+ applicationAttributeInfo.BackupAgent = TryGetTypeProperty (ca, "BackupAgent");
+ applicationAttributeInfo.ManageSpaceActivity = TryGetTypeProperty (ca, "ManageSpaceActivity");
+ }
+ }
+ }
+
+ return (registerInfo, attrInfo);
+ }
+
+ static readonly HashSet KnownComponentAttributes = new (StringComparer.Ordinal) {
+ "ActivityAttribute",
+ "ServiceAttribute",
+ "BroadcastReceiverAttribute",
+ "ContentProviderAttribute",
+ "ApplicationAttribute",
+ "InstrumentationAttribute",
+ };
+
+ static TypeAttributeInfo CreateTypeAttributeInfo (string attrName)
+ {
+ return attrName == "ApplicationAttribute"
+ ? new ApplicationAttributeInfo ()
+ : new TypeAttributeInfo (attrName);
+ }
+
+ static bool IsKnownComponentAttribute (string attrName) => KnownComponentAttributes.Contains (attrName);
+
+ internal static string? GetCustomAttributeName (CustomAttribute ca, MetadataReader reader)
+ {
+ if (ca.Constructor.Kind == HandleKind.MemberReference) {
+ var memberRef = reader.GetMemberReference ((MemberReferenceHandle)ca.Constructor);
+ if (memberRef.Parent.Kind == HandleKind.TypeReference) {
+ var typeRef = reader.GetTypeReference ((TypeReferenceHandle)memberRef.Parent);
+ return reader.GetString (typeRef.Name);
+ }
+ } else if (ca.Constructor.Kind == HandleKind.MethodDefinition) {
+ var methodDef = reader.GetMethodDefinition ((MethodDefinitionHandle)ca.Constructor);
+ var declaringType = reader.GetTypeDefinition (methodDef.GetDeclaringType ());
+ return reader.GetString (declaringType.Name);
+ }
+ return null;
+ }
+
+ internal RegisterInfo ParseRegisterAttribute (CustomAttribute ca)
+ {
+ return ParseRegisterInfo (DecodeAttribute (ca));
+ }
+
+ internal CustomAttributeValue DecodeAttribute (CustomAttribute ca)
+ {
+ return ca.DecodeValue (customAttributeTypeProvider);
+ }
+
+ RegisterInfo ParseRegisterInfo (CustomAttributeValue value)
+ {
+
+ string jniName = "";
+ string? signature = null;
+ string? connector = null;
+ bool doNotGenerateAcw = false;
+
+ if (value.FixedArguments.Length > 0) {
+ jniName = (string?)value.FixedArguments [0].Value ?? "";
+ }
+ if (value.FixedArguments.Length > 1) {
+ signature = (string?)value.FixedArguments [1].Value;
+ }
+ if (value.FixedArguments.Length > 2) {
+ connector = (string?)value.FixedArguments [2].Value;
+ }
+
+ if (TryGetNamedArgument (value, "DoNotGenerateAcw", out var doNotGenerateAcwValue)) {
+ doNotGenerateAcw = doNotGenerateAcwValue;
+ }
+
+ return new RegisterInfo {
+ JniName = jniName,
+ Signature = signature,
+ Connector = connector,
+ DoNotGenerateAcw = doNotGenerateAcw,
+ };
+ }
+
+ string? TryGetTypeProperty (CustomAttribute ca, string propertyName)
+ {
+ var value = DecodeAttribute (ca);
+ if (TryGetNamedArgument (value, propertyName, out var typeName) && !string.IsNullOrEmpty (typeName)) {
+ return typeName;
+ }
+ return null;
+ }
+
+ string? TryGetNameProperty (CustomAttribute ca)
+ {
+ var name = TryGetTypeProperty (ca, "Name");
+ if (!string.IsNullOrEmpty (name)) {
+ return name;
+ }
+
+ var value = DecodeAttribute (ca);
+
+ // Fall back to first constructor argument (e.g., [CustomJniName("...")])
+ if (value.FixedArguments.Length > 0 && value.FixedArguments [0].Value is string ctorName && !string.IsNullOrEmpty (ctorName)) {
+ return ctorName;
+ }
+
+ return null;
+ }
+
+ static bool TryGetNamedArgument (CustomAttributeValue value, string argumentName, [MaybeNullWhen (false)] out T argumentValue) where T : notnull
+ {
+ foreach (var named in value.NamedArguments) {
+ if (named.Name == argumentName && named.Value is T typedValue) {
+ argumentValue = typedValue;
+ return true;
+ }
+ }
+ argumentValue = default;
+ return false;
+ }
+
+ public void Dispose ()
+ {
+ peReader.Dispose ();
+ }
+}
+
+///
+/// Parsed [Register] attribute data for a type or method.
+///
+sealed record RegisterInfo
+{
+ public required string JniName { get; init; }
+ public string? Signature { get; init; }
+ public string? Connector { get; init; }
+ 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;
+ public string? JniName { get; set; }
+}
+
+sealed class ApplicationAttributeInfo () : TypeAttributeInfo ("ApplicationAttribute")
+{
+ public string? BackupAgent { get; set; }
+ public string? ManageSpaceActivity { get; set; }
+}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/CustomAttributeTypeProvider.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/CustomAttributeTypeProvider.cs
new file mode 100644
index 00000000000..f3441d1a475
--- /dev/null
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/CustomAttributeTypeProvider.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+
+namespace Microsoft.Android.Sdk.TrimmableTypeMap;
+
+///
+/// Minimal ICustomAttributeTypeProvider implementation for decoding
+/// custom attribute values via System.Reflection.Metadata.
+///
+sealed class CustomAttributeTypeProvider (MetadataReader reader) : ICustomAttributeTypeProvider
+{
+ Dictionary? enumTypeCache;
+
+ public string GetPrimitiveType (PrimitiveTypeCode typeCode) => typeCode.ToString ();
+
+ public string GetTypeFromDefinition (MetadataReader metadataReader, TypeDefinitionHandle handle, byte rawTypeKind)
+ => MetadataTypeNameResolver.GetTypeFromDefinition (metadataReader, handle, rawTypeKind);
+
+ public string GetTypeFromReference (MetadataReader metadataReader, TypeReferenceHandle handle, byte rawTypeKind)
+ => MetadataTypeNameResolver.GetTypeFromReference (metadataReader, handle, rawTypeKind);
+
+ public string GetTypeFromSerializedName (string name) => name;
+
+ public PrimitiveTypeCode GetUnderlyingEnumType (string type)
+ {
+ if (enumTypeCache == null) {
+ enumTypeCache = BuildEnumTypeCache ();
+ }
+
+ if (enumTypeCache.TryGetValue (type, out var code)) {
+ return code;
+ }
+
+ // Default to Int32 for enums defined in other assemblies
+ return PrimitiveTypeCode.Int32;
+ }
+
+ Dictionary BuildEnumTypeCache ()
+ {
+ var cache = new Dictionary ();
+
+ foreach (var typeHandle in reader.TypeDefinitions) {
+ var typeDef = reader.GetTypeDefinition (typeHandle);
+
+ // Only process enum types
+ if (!IsEnum (typeDef))
+ continue;
+
+ var fullName = GetTypeFromDefinition (reader, typeHandle, rawTypeKind: 0);
+ cache [fullName] = GetEnumUnderlyingTypeCode (typeDef);
+ }
+
+ return cache;
+ }
+
+ bool IsEnum (TypeDefinition typeDef)
+ {
+ var baseType = typeDef.BaseType;
+ if (baseType.IsNil)
+ return false;
+
+ string? baseFullName = baseType.Kind switch {
+ HandleKind.TypeReference => GetTypeFromReference (reader, (TypeReferenceHandle)baseType, rawTypeKind: 0),
+ HandleKind.TypeDefinition => GetTypeFromDefinition (reader, (TypeDefinitionHandle)baseType, rawTypeKind: 0),
+ _ => null,
+ };
+
+ return baseFullName == "System.Enum";
+ }
+
+ PrimitiveTypeCode GetEnumUnderlyingTypeCode (TypeDefinition typeDef)
+ {
+ // For enums, the first instance field is the underlying value__ field
+ foreach (var fieldHandle in typeDef.GetFields ()) {
+ var field = reader.GetFieldDefinition (fieldHandle);
+ if ((field.Attributes & System.Reflection.FieldAttributes.Static) != 0)
+ continue;
+
+ var sig = field.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null);
+ return sig switch {
+ "System.Byte" => PrimitiveTypeCode.Byte,
+ "System.SByte" => PrimitiveTypeCode.SByte,
+ "System.Int16" => PrimitiveTypeCode.Int16,
+ "System.UInt16" => PrimitiveTypeCode.UInt16,
+ "System.Int32" => PrimitiveTypeCode.Int32,
+ "System.UInt32" => PrimitiveTypeCode.UInt32,
+ "System.Int64" => PrimitiveTypeCode.Int64,
+ "System.UInt64" => PrimitiveTypeCode.UInt64,
+ _ => PrimitiveTypeCode.Int32,
+ };
+ }
+
+ return PrimitiveTypeCode.Int32;
+ }
+
+ public string GetSystemType () => "System.Type";
+
+ public string GetSZArrayType (string elementType) => $"{elementType}[]";
+
+ public bool IsSystemType (string type) => type == "System.Type" || type == "Type";
+}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
new file mode 100644
index 00000000000..85472f1b3ba
--- /dev/null
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
@@ -0,0 +1,173 @@
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Android.Sdk.TrimmableTypeMap;
+
+///
+/// Represents a Java peer type discovered during assembly scanning.
+/// Contains all data needed by downstream generators (TypeMap IL, UCO wrappers, JCW Java sources).
+/// Generators consume this data model — they never touch PEReader/MetadataReader.
+///
+sealed record JavaPeerInfo
+{
+ ///
+ /// JNI type name, e.g., "android/app/Activity".
+ /// Extracted from the [Register] attribute.
+ ///
+ 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".
+ ///
+ public required string ManagedTypeName { get; init; }
+
+ ///
+ /// Assembly name the type belongs to, e.g., "Mono.Android".
+ ///
+ public required string AssemblyName { get; init; }
+
+ ///
+ /// JNI name of the base Java type, e.g., "android/app/Activity" for a type
+ /// that extends Activity. Null for java/lang/Object or types without a Java base.
+ /// Needed by JCW Java source generation ("extends" clause).
+ ///
+ public string? BaseJavaName { get; init; }
+
+ ///
+ /// JNI names of Java interfaces this type implements, e.g., ["android/view/View$OnClickListener"].
+ /// Needed by JCW Java source generation ("implements" clause).
+ ///
+ public IReadOnlyList ImplementedInterfaceJavaNames { get; init; } = Array.Empty ();
+
+ public bool IsInterface { get; init; }
+ public bool IsAbstract { get; init; }
+
+ ///
+ /// If true, this is a Managed Callable Wrapper (MCW) binding type.
+ /// No JCW or RegisterNatives will be generated for it.
+ ///
+ public bool DoNotGenerateAcw { get; init; }
+
+ ///
+ /// Types with component attributes ([Activity], [Service], etc.),
+ /// custom views from layout XML, or manifest-declared components
+ /// are unconditionally preserved (not trimmable).
+ ///
+ 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.
+ ///
+ public ActivationCtorInfo? ActivationCtor { get; init; }
+
+ ///
+ /// For interfaces and abstract types, the name of the invoker type
+ /// used to instantiate instances from Java.
+ ///
+ public string? InvokerTypeName { get; init; }
+
+ ///
+ /// True if this is an open generic type definition.
+ /// Generic types get TypeMap entries but CreateInstance throws NotSupportedException.
+ ///
+ 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; }
+
+ ///
+ /// 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 how to call the activation constructor for a Java peer type.
+///
+sealed record ActivationCtorInfo
+{
+ ///
+ /// The type that declares the activation constructor.
+ /// May be the type itself or a base type.
+ ///
+ public required string DeclaringTypeName { get; init; }
+
+ ///
+ /// The assembly containing the declaring type.
+ ///
+ public required string DeclaringAssemblyName { get; init; }
+
+ ///
+ /// The style of activation constructor found.
+ ///
+ public required ActivationCtorStyle Style { get; init; }
+}
+
+enum ActivationCtorStyle
+{
+ ///
+ /// Xamarin.Android style: (IntPtr handle, JniHandleOwnership transfer)
+ ///
+ XamarinAndroid,
+
+ ///
+ /// Java.Interop style: (ref JniObjectReference reference, JniObjectReferenceOptions options)
+ ///
+ JavaInterop,
+}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
new file mode 100644
index 00000000000..469d6345596
--- /dev/null
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
@@ -0,0 +1,735 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+namespace Microsoft.Android.Sdk.TrimmableTypeMap;
+
+///
+/// Scans assemblies for Java peer types using System.Reflection.Metadata.
+/// Two-phase architecture:
+/// Phase 1: Build per-assembly indices (fast, O(1) lookups)
+/// Phase 2: Analyze types using cached indices
+///
+sealed class JavaPeerScanner : IDisposable
+{
+ readonly Dictionary assemblyCache = new (StringComparer.Ordinal);
+ readonly Dictionary<(string typeName, string assemblyName), ActivationCtorInfo> activationCtorCache = new ();
+
+ ///
+ /// Resolves a type name + assembly name to a TypeDefinitionHandle + AssemblyIndex.
+ /// Checks the specified assembly (by name) in the assembly cache.
+ ///
+ bool TryResolveType (string typeName, string assemblyName, out TypeDefinitionHandle handle, [NotNullWhen (true)] out AssemblyIndex? resolvedIndex)
+ {
+ if (assemblyCache.TryGetValue (assemblyName, out resolvedIndex) &&
+ resolvedIndex.TypesByFullName.TryGetValue (typeName, out handle)) {
+ return true;
+ }
+ handle = default;
+ resolvedIndex = null;
+ return false;
+ }
+
+ ///
+ /// Resolves a TypeReferenceHandle to (fullName, assemblyName), correctly handling
+ /// nested types whose ResolutionScope is another TypeReference.
+ ///
+ static (string fullName, string assemblyName) ResolveTypeReference (TypeReferenceHandle handle, AssemblyIndex index)
+ {
+ var typeRef = index.Reader.GetTypeReference (handle);
+ var name = index.Reader.GetString (typeRef.Name);
+ var ns = index.Reader.GetString (typeRef.Namespace);
+
+ var scope = typeRef.ResolutionScope;
+ switch (scope.Kind) {
+ case HandleKind.AssemblyReference: {
+ var asmRef = index.Reader.GetAssemblyReference ((AssemblyReferenceHandle)scope);
+ var fullName = MetadataTypeNameResolver.JoinNamespaceAndName (ns, name);
+ return (fullName, index.Reader.GetString (asmRef.Name));
+ }
+ case HandleKind.TypeReference: {
+ // Nested type: recurse to get the declaring type's full name and assembly
+ var (parentFullName, assemblyName) = ResolveTypeReference ((TypeReferenceHandle)scope, index);
+ return (MetadataTypeNameResolver.JoinNestedTypeName (parentFullName, name), assemblyName);
+ }
+ default: {
+ var fullName = MetadataTypeNameResolver.JoinNamespaceAndName (ns, name);
+ return (fullName, index.AssemblyName);
+ }
+ }
+ }
+
+ ///
+ /// Looks up the [Register] JNI name for a type identified by name + assembly.
+ ///
+ string? ResolveRegisterJniName (string typeName, string assemblyName)
+ {
+ if (TryResolveType (typeName, assemblyName, out var handle, out var resolvedIndex) &&
+ resolvedIndex.RegisterInfoByType.TryGetValue (handle, out var regInfo)) {
+ return regInfo.JniName;
+ }
+ return null;
+ }
+
+ ///
+ /// Phase 1: Build indices for all assemblies.
+ /// Phase 2: Scan all types and produce JavaPeerInfo records.
+ ///
+ public List Scan (IReadOnlyList assemblyPaths)
+ {
+ // Phase 1: Build indices for all assemblies
+ foreach (var path in assemblyPaths) {
+ var index = AssemblyIndex.Create (path);
+ assemblyCache [index.AssemblyName] = index;
+ }
+
+ // Phase 2: Analyze types using cached indices
+ var resultsByManagedName = new Dictionary (StringComparer.Ordinal);
+
+ foreach (var index in assemblyCache.Values) {
+ ScanAssembly (index, resultsByManagedName);
+ }
+
+ // Phase 3: Force unconditional on types referenced by [Application] attributes
+ ForceUnconditionalCrossReferences (resultsByManagedName, assemblyCache);
+
+ return new List (resultsByManagedName.Values);
+ }
+
+ ///
+ /// Types referenced by [Application(BackupAgent = typeof(X))] or
+ /// [Application(ManageSpaceActivity = typeof(X))] must be unconditional,
+ /// because the manifest will reference them even if nothing else does.
+ ///
+ static void ForceUnconditionalCrossReferences (Dictionary resultsByManagedName, Dictionary assemblyCache)
+ {
+ foreach (var index in assemblyCache.Values) {
+ foreach (var attrInfo in index.AttributesByType.Values) {
+ if (attrInfo is ApplicationAttributeInfo applicationAttributeInfo) {
+ ForceUnconditionalIfPresent (resultsByManagedName, applicationAttributeInfo.BackupAgent);
+ ForceUnconditionalIfPresent (resultsByManagedName, applicationAttributeInfo.ManageSpaceActivity);
+ }
+ }
+ }
+ }
+
+ static void ForceUnconditionalIfPresent (Dictionary resultsByManagedName, string? managedTypeName)
+ {
+ if (managedTypeName is null) {
+ return;
+ }
+
+ managedTypeName = managedTypeName.Trim ();
+ if (managedTypeName.Length == 0) {
+ return;
+ }
+
+ // Try exact match first (handles both plain and assembly-qualified names)
+ if (resultsByManagedName.TryGetValue (managedTypeName, out var peer)) {
+ resultsByManagedName [managedTypeName] = peer with { IsUnconditional = true };
+ return;
+ }
+
+ // TryGetTypeProperty may return assembly-qualified names like "Ns.Type, Assembly, ..."
+ // Strip to just the type name for lookup
+ var commaIndex = managedTypeName.IndexOf (',');
+ if (commaIndex <= 0) {
+ return;
+ }
+
+ var typeName = managedTypeName.Substring (0, commaIndex).Trim ();
+ if (typeName.Length > 0 && resultsByManagedName.TryGetValue (typeName, out peer)) {
+ resultsByManagedName [typeName] = peer with { IsUnconditional = true };
+ }
+ }
+
+ void ScanAssembly (AssemblyIndex index, Dictionary results)
+ {
+ foreach (var typeHandle in index.Reader.TypeDefinitions) {
+ var typeDef = index.Reader.GetTypeDefinition (typeHandle);
+
+ // Skip module-level types
+ if (index.Reader.GetString (typeDef.Name) == "") {
+ continue;
+ }
+
+ // Determine the JNI name and whether this is a known Java peer.
+ // Priority:
+ // 1. [Register] attribute → use JNI name from attribute
+ // 2. Component attribute Name property → convert dots to slashes
+ // 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);
+ index.AttributesByType.TryGetValue (typeHandle, out var attrInfo);
+
+ 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, compatJniName) = ComputeAutoJniNames (typeDef, index);
+ } else {
+ continue;
+ }
+ }
+
+ var fullName = MetadataTypeNameResolver.GetFullName (typeDef, index.Reader);
+
+ var isInterface = (typeDef.Attributes & TypeAttributes.Interface) != 0;
+ var isAbstract = (typeDef.Attributes & TypeAttributes.Abstract) != 0;
+ var isGenericDefinition = typeDef.GetGenericParameters ().Count > 0;
+
+ var isUnconditional = attrInfo is not null;
+ string? invokerTypeName = null;
+
+ // Resolve base Java type name
+ var baseJavaName = ResolveBaseJavaName (typeDef, index, results);
+
+ // Resolve implemented Java interface names
+ var implementedInterfaces = ResolveImplementedInterfaceJavaNames (typeDef, index);
+
+ // 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);
+
+ // For interfaces/abstract types, try to find invoker type name
+ if (isInterface || isAbstract) {
+ invokerTypeName = TryFindInvokerTypeName (fullName, typeHandle, index);
+ }
+
+ var peer = new JavaPeerInfo {
+ JavaName = jniName,
+ CompatJniName = compatJniName,
+ ManagedTypeName = fullName,
+ AssemblyName = index.AssemblyName,
+ BaseJavaName = baseJavaName,
+ ImplementedInterfaceJavaNames = implementedInterfaces,
+ IsInterface = isInterface,
+ IsAbstract = isAbstract,
+ DoNotGenerateAcw = doNotGenerateAcw,
+ IsUnconditional = isUnconditional,
+ MarshalMethods = marshalMethods,
+ ActivationCtor = activationCtor,
+ InvokerTypeName = invokerTypeName,
+ IsGenericDefinition = isGenericDefinition,
+ };
+
+ results [fullName] = peer;
+ }
+ }
+
+ 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;
+ }
+
+ methods.Add (new MarshalMethodInfo {
+ JniName = registerInfo.JniName,
+ JniSignature = registerInfo.Signature ?? "()V",
+ Connector = registerInfo.Connector,
+ ManagedMethodName = index.Reader.GetString (methodDef.Name),
+ IsConstructor = registerInfo.JniName == "" || registerInfo.JniName == ".ctor",
+ ThrownNames = exportInfo?.ThrownNames,
+ SuperArgumentsString = exportInfo?.SuperArgumentsString,
+ });
+ }
+
+ string? ResolveBaseJavaName (TypeDefinition typeDef, AssemblyIndex index, Dictionary results)
+ {
+ var baseInfo = GetBaseTypeInfo (typeDef, index);
+ if (baseInfo is null) {
+ return null;
+ }
+
+ var (baseTypeName, baseAssemblyName) = baseInfo.Value;
+
+ // First try [Register] attribute
+ var registerJniName = ResolveRegisterJniName (baseTypeName, baseAssemblyName);
+ if (registerJniName is not null) {
+ return registerJniName;
+ }
+
+ // Fall back to already-scanned results (component-attributed or CRC64-computed peers)
+ if (results.TryGetValue (baseTypeName, out var basePeer)) {
+ return basePeer.JavaName;
+ }
+
+ return null;
+ }
+
+ List ResolveImplementedInterfaceJavaNames (TypeDefinition typeDef, AssemblyIndex index)
+ {
+ var result = new List ();
+ var interfaceImpls = typeDef.GetInterfaceImplementations ();
+
+ foreach (var implHandle in interfaceImpls) {
+ var impl = index.Reader.GetInterfaceImplementation (implHandle);
+ var ifaceJniName = ResolveInterfaceJniName (impl.Interface, index);
+ if (ifaceJniName is not null) {
+ result.Add (ifaceJniName);
+ }
+ }
+
+ return result;
+ }
+
+ string? ResolveInterfaceJniName (EntityHandle interfaceHandle, AssemblyIndex index)
+ {
+ var resolved = ResolveEntityHandle (interfaceHandle, index);
+ return resolved is not null ? ResolveRegisterJniName (resolved.Value.typeName, resolved.Value.assemblyName) : null;
+ }
+
+ 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);
+ if (activationCtorCache.TryGetValue (cacheKey, out var cached)) {
+ return cached;
+ }
+
+ // Check this type's constructors
+ var ownCtor = FindActivationCtorOnType (typeDef, index);
+ if (ownCtor is not null) {
+ var info = new ActivationCtorInfo { DeclaringTypeName = typeName, DeclaringAssemblyName = index.AssemblyName, Style = ownCtor.Value };
+ activationCtorCache [cacheKey] = info;
+ return info;
+ }
+
+ // Walk base type hierarchy
+ var baseInfo = GetBaseTypeInfo (typeDef, index);
+ if (baseInfo is not null) {
+ var (baseTypeName, baseAssemblyName) = baseInfo.Value;
+ if (TryResolveType (baseTypeName, baseAssemblyName, out var baseHandle, out var baseIndex)) {
+ var baseTypeDef = baseIndex.Reader.GetTypeDefinition (baseHandle);
+ var result = ResolveActivationCtor (baseTypeName, baseTypeDef, baseIndex);
+ if (result is not null) {
+ activationCtorCache [cacheKey] = result;
+ }
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ static ActivationCtorStyle? FindActivationCtorOnType (TypeDefinition typeDef, AssemblyIndex index)
+ {
+ foreach (var methodHandle in typeDef.GetMethods ()) {
+ var method = index.Reader.GetMethodDefinition (methodHandle);
+ var name = index.Reader.GetString (method.Name);
+
+ if (name != ".ctor") {
+ continue;
+ }
+
+ var sig = method.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default);
+
+ // XI style: (IntPtr, JniHandleOwnership)
+ if (sig.ParameterTypes.Length == 2 &&
+ sig.ParameterTypes [0] == "System.IntPtr" &&
+ sig.ParameterTypes [1] == "Android.Runtime.JniHandleOwnership") {
+ return ActivationCtorStyle.XamarinAndroid;
+ }
+
+ // JI style: (ref JniObjectReference, JniObjectReferenceOptions)
+ if (sig.ParameterTypes.Length == 2 &&
+ (sig.ParameterTypes [0] == "Java.Interop.JniObjectReference&" || sig.ParameterTypes [0] == "Java.Interop.JniObjectReference") &&
+ sig.ParameterTypes [1] == "Java.Interop.JniObjectReferenceOptions") {
+ return ActivationCtorStyle.JavaInterop;
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Resolves a TypeSpecificationHandle (generic instantiation) to the underlying
+ /// type's (fullName, assemblyName) by reading the raw signature blob.
+ ///
+ static (string fullName, string assemblyName)? ResolveTypeSpecification (TypeSpecificationHandle specHandle, AssemblyIndex index)
+ {
+ var typeSpec = index.Reader.GetTypeSpecification (specHandle);
+ var blobReader = index.Reader.GetBlobReader (typeSpec.Signature);
+
+ // Generic instantiation blob: GENERICINST (CLASS|VALUETYPE) coded-token count args...
+ var elementType = blobReader.ReadByte ();
+ if (elementType != 0x15) { // ELEMENT_TYPE_GENERICINST
+ return null;
+ }
+
+ var classOrValueType = blobReader.ReadByte ();
+ if (classOrValueType != 0x12 && classOrValueType != 0x11) { // CLASS or VALUETYPE
+ return null;
+ }
+
+ // TypeDefOrRefOrSpec coded index: 2 tag bits (0=TypeDef, 1=TypeRef, 2=TypeSpec)
+ var codedToken = blobReader.ReadCompressedInteger ();
+ var tag = codedToken & 0x3;
+ var row = codedToken >> 2;
+
+ switch (tag) {
+ case 0: { // TypeDef
+ var handle = MetadataTokens.TypeDefinitionHandle (row);
+ var baseDef = index.Reader.GetTypeDefinition (handle);
+ return (MetadataTypeNameResolver.GetFullName (baseDef, index.Reader), index.AssemblyName);
+ }
+ case 1: // TypeRef
+ return ResolveTypeReference (MetadataTokens.TypeReferenceHandle (row), index);
+ default:
+ return null;
+ }
+ }
+
+ ///
+ /// Resolves an EntityHandle (TypeDef, TypeRef, or TypeSpec) to (typeName, assemblyName).
+ /// Shared by base type resolution, interface resolution, and any handle-to-name lookup.
+ ///
+ (string typeName, string assemblyName)? ResolveEntityHandle (EntityHandle handle, AssemblyIndex index)
+ {
+ switch (handle.Kind) {
+ case HandleKind.TypeDefinition: {
+ var td = index.Reader.GetTypeDefinition ((TypeDefinitionHandle)handle);
+ return (MetadataTypeNameResolver.GetFullName (td, index.Reader), index.AssemblyName);
+ }
+ case HandleKind.TypeReference:
+ return ResolveTypeReference ((TypeReferenceHandle)handle, index);
+ case HandleKind.TypeSpecification:
+ return ResolveTypeSpecification ((TypeSpecificationHandle)handle, index);
+ default:
+ return null;
+ }
+ }
+
+ (string typeName, string assemblyName)? GetBaseTypeInfo (TypeDefinition typeDef, AssemblyIndex index)
+ {
+ return typeDef.BaseType.IsNil ? null : ResolveEntityHandle (typeDef.BaseType, index);
+ }
+
+ string? TryFindInvokerTypeName (string typeName, TypeDefinitionHandle typeHandle, AssemblyIndex index)
+ {
+ // First, check the [Register] attribute's connector arg (3rd arg).
+ // In real Mono.Android, interfaces have [Register("jni/name", "", "InvokerTypeName, Assembly")]
+ // where the connector contains the assembly-qualified invoker type name.
+ if (index.RegisterInfoByType.TryGetValue (typeHandle, out var registerInfo) && registerInfo.Connector is not null) {
+ var connector = registerInfo.Connector;
+ // The connector may be "TypeName" or "TypeName, Assembly, Version=..., Culture=..., PublicKeyToken=..."
+ // We want just the type name (before the first comma, if any)
+ var commaIndex = connector.IndexOf (',');
+ if (commaIndex > 0) {
+ return connector.Substring (0, commaIndex).Trim ();
+ }
+ if (connector.Length > 0) {
+ return connector;
+ }
+ }
+
+ // Fallback: convention-based lookup — invoker type is TypeName + "Invoker"
+ var invokerName = $"{typeName}Invoker";
+ if (index.TypesByFullName.ContainsKey (invokerName)) {
+ return invokerName;
+ }
+ return null;
+ }
+
+ public void Dispose ()
+ {
+ foreach (var index in assemblyCache.Values) {
+ index.Dispose ();
+ }
+ assemblyCache.Clear ();
+ }
+
+ readonly Dictionary extendsJavaPeerCache = new (StringComparer.Ordinal);
+
+ ///
+ /// Check if a type extends a known Java peer (has [Register] or component attribute)
+ /// by walking the base type chain. Results are cached; false-before-recurse prevents cycles.
+ ///
+ bool ExtendsJavaPeer (TypeDefinition typeDef, AssemblyIndex index)
+ {
+ var fullName = MetadataTypeNameResolver.GetFullName (typeDef, index.Reader);
+ var key = $"{index.AssemblyName}:{fullName}";
+
+ if (extendsJavaPeerCache.TryGetValue (key, out var cached)) {
+ return cached;
+ }
+
+ // Mark as false to prevent cycles, then compute
+ extendsJavaPeerCache [key] = false;
+
+ var baseInfo = GetBaseTypeInfo (typeDef, index);
+ if (baseInfo is null) {
+ return false;
+ }
+
+ var (baseTypeName, baseAssemblyName) = baseInfo.Value;
+
+ if (!TryResolveType (baseTypeName, baseAssemblyName, out var baseHandle, out var baseIndex)) {
+ return false;
+ }
+
+ // Direct hit: base has [Register] or component attribute
+ if (baseIndex.RegisterInfoByType.ContainsKey (baseHandle)) {
+ extendsJavaPeerCache [key] = true;
+ return true;
+ }
+ if (baseIndex.AttributesByType.ContainsKey (baseHandle)) {
+ extendsJavaPeerCache [key] = true;
+ return true;
+ }
+
+ // Recurse up the hierarchy
+ var baseDef = baseIndex.Reader.GetTypeDefinition (baseHandle);
+ var result = ExtendsJavaPeer (baseDef, baseIndex);
+ extendsJavaPeerCache [key] = result;
+ return result;
+ }
+
+ ///
+ /// 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.
+ /// 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 jniName, string compatJniName) ComputeAutoJniNames (TypeDefinition typeDef, AssemblyIndex index)
+ {
+ var (typeName, parentJniName, ns) = ComputeTypeNameParts (typeDef, index);
+
+ if (parentJniName is not null) {
+ var name = $"{parentJniName}_{typeName}";
+ return (name, name);
+ }
+
+ var packageName = GetCrc64PackageName (ns, index.AssemblyName);
+ var jniName = $"{packageName}/{typeName}";
+
+ string compatName = ns.Length == 0
+ ? typeName
+ : $"{ns.ToLowerInvariant ().Replace ('.', '/')}/{typeName}";
+
+ return (jniName, compatName);
+ }
+
+ ///
+ /// Builds the type name part (handling nesting) and returns either a parent's
+ /// registered JNI name or the outermost namespace.
+ /// Matches JavaNativeTypeManager.ToJniName behavior: walks up declaring types
+ /// and if a parent has [Register] or a component attribute JNI name, uses that
+ /// as prefix instead of computing CRC64 from the namespace.
+ ///
+ static (string typeName, string? parentJniName, string ns) ComputeTypeNameParts (TypeDefinition typeDef, AssemblyIndex index)
+ {
+ var firstName = index.Reader.GetString (typeDef.Name).Replace ('`', '_');
+
+ // Fast path: non-nested types (the vast majority)
+ if (!typeDef.IsNested) {
+ return (firstName, null, index.Reader.GetString (typeDef.Namespace));
+ }
+
+ // Nested type: walk up declaring types, collecting name parts
+ var nameParts = new List (4) { firstName };
+ var current = typeDef;
+ string? parentJniName = null;
+
+ do {
+ var parentHandle = current.GetDeclaringType ();
+ current = index.Reader.GetTypeDefinition (parentHandle);
+
+ // Check if the parent has a registered JNI name
+ if (index.RegisterInfoByType.TryGetValue (parentHandle, out var parentRegister) && !string.IsNullOrEmpty (parentRegister.JniName)) {
+ parentJniName = parentRegister.JniName;
+ break;
+ }
+ if (index.AttributesByType.TryGetValue (parentHandle, out var parentAttr) && parentAttr.JniName is not null) {
+ parentJniName = parentAttr.JniName;
+ break;
+ }
+
+ nameParts.Add (index.Reader.GetString (current.Name).Replace ('`', '_'));
+ } while (current.IsNested);
+
+ nameParts.Reverse ();
+ var typeName = string.Join ("_", nameParts);
+ var ns = index.Reader.GetString (current.Namespace);
+
+ return (typeName, parentJniName, ns);
+ }
+
+ static string GetCrc64PackageName (string ns, string assemblyName)
+ {
+ // Only Mono.Android preserves the namespace directly
+ if (assemblyName == "Mono.Android") {
+ return ns.ToLowerInvariant ().Replace ('.', '/');
+ }
+
+ var data = System.Text.Encoding.UTF8.GetBytes ($"{ns}:{assemblyName}");
+ var hash = System.IO.Hashing.Crc64.Hash (data);
+ return $"crc64{BitConverter.ToString (hash).Replace ("-", "").ToLowerInvariant ()}";
+ }
+}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/MetadataTypeNameResolver.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/MetadataTypeNameResolver.cs
new file mode 100644
index 00000000000..41394034f51
--- /dev/null
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/MetadataTypeNameResolver.cs
@@ -0,0 +1,49 @@
+using System.Reflection.Metadata;
+
+namespace Microsoft.Android.Sdk.TrimmableTypeMap;
+
+///
+/// Shared logic for resolving fully qualified type names from metadata handles.
+/// Used by both and .
+///
+static class MetadataTypeNameResolver
+{
+ internal static string JoinNamespaceAndName (string ns, string name)
+ {
+ return ns.Length > 0 ? $"{ns}.{name}" : name;
+ }
+
+ internal static string JoinNestedTypeName (string parentName, string name)
+ {
+ return $"{parentName}+{name}";
+ }
+
+ public static string GetFullName (TypeDefinition typeDef, MetadataReader reader)
+ {
+ var name = reader.GetString (typeDef.Name);
+ var ns = reader.GetString (typeDef.Namespace);
+ if (typeDef.IsNested) {
+ var declaringType = reader.GetTypeDefinition (typeDef.GetDeclaringType ());
+ var parentName = GetFullName (declaringType, reader);
+ return JoinNestedTypeName (parentName, name);
+ }
+ return JoinNamespaceAndName (ns, name);
+ }
+
+ public static string GetTypeFromDefinition (MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind)
+ {
+ return GetFullName (reader.GetTypeDefinition (handle), reader);
+ }
+
+ public static string GetTypeFromReference (MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind)
+ {
+ var typeRef = reader.GetTypeReference (handle);
+ var name = reader.GetString (typeRef.Name);
+ if (typeRef.ResolutionScope.Kind == HandleKind.TypeReference) {
+ var parent = GetTypeFromReference (reader, (TypeReferenceHandle)typeRef.ResolutionScope, rawTypeKind);
+ return JoinNestedTypeName (parent, name);
+ }
+ var ns = reader.GetString (typeRef.Namespace);
+ return JoinNamespaceAndName (ns, name);
+ }
+}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs
new file mode 100644
index 00000000000..87ed078adf2
--- /dev/null
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Immutable;
+using System.Reflection.Metadata;
+
+namespace Microsoft.Android.Sdk.TrimmableTypeMap;
+
+///
+/// Minimal ISignatureTypeProvider implementation for decoding method
+/// signatures via System.Reflection.Metadata.
+/// Returns fully qualified type name strings.
+///
+sealed class SignatureTypeProvider : ISignatureTypeProvider
+{
+ public static readonly SignatureTypeProvider Instance = new ();
+
+ public string GetPrimitiveType (PrimitiveTypeCode typeCode) => typeCode switch {
+ PrimitiveTypeCode.Void => "System.Void",
+ PrimitiveTypeCode.Boolean => "System.Boolean",
+ PrimitiveTypeCode.Char => "System.Char",
+ PrimitiveTypeCode.SByte => "System.SByte",
+ PrimitiveTypeCode.Byte => "System.Byte",
+ PrimitiveTypeCode.Int16 => "System.Int16",
+ PrimitiveTypeCode.UInt16 => "System.UInt16",
+ PrimitiveTypeCode.Int32 => "System.Int32",
+ PrimitiveTypeCode.UInt32 => "System.UInt32",
+ PrimitiveTypeCode.Int64 => "System.Int64",
+ PrimitiveTypeCode.UInt64 => "System.UInt64",
+ PrimitiveTypeCode.Single => "System.Single",
+ PrimitiveTypeCode.Double => "System.Double",
+ PrimitiveTypeCode.String => "System.String",
+ PrimitiveTypeCode.Object => "System.Object",
+ PrimitiveTypeCode.IntPtr => "System.IntPtr",
+ PrimitiveTypeCode.UIntPtr => "System.UIntPtr",
+ PrimitiveTypeCode.TypedReference => "System.TypedReference",
+ _ => typeCode.ToString (),
+ };
+
+ public string GetTypeFromDefinition (MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind)
+ => MetadataTypeNameResolver.GetTypeFromDefinition (reader, handle, rawTypeKind);
+
+ public string GetTypeFromReference (MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind)
+ => MetadataTypeNameResolver.GetTypeFromReference (reader, handle, rawTypeKind);
+
+ public string GetTypeFromSpecification (MetadataReader reader, object? genericContext, TypeSpecificationHandle handle, byte rawTypeKind)
+ {
+ var typeSpec = reader.GetTypeSpecification (handle);
+ return typeSpec.DecodeSignature (this, genericContext);
+ }
+
+ public string GetSZArrayType (string elementType) => $"{elementType}[]";
+ public string GetArrayType (string elementType, ArrayShape shape) => $"{elementType}[{new string (',', shape.Rank - 1)}]";
+ public string GetByReferenceType (string elementType) => $"{elementType}&";
+ public string GetPointerType (string elementType) => $"{elementType}*";
+ public string GetPinnedType (string elementType) => elementType;
+ public string GetModifiedType (string modifier, string unmodifiedType, bool isRequired) => unmodifiedType;
+
+ public string GetGenericInstantiation (string genericType, ImmutableArray typeArguments)
+ {
+ return $"{genericType}<{string.Join (",", typeArguments)}>";
+ }
+
+ public string GetGenericTypeParameter (object? genericContext, int index) => $"!{index}";
+ public string GetGenericMethodParameter (object? genericContext, int index) => $"!!{index}";
+
+ public string GetFunctionPointerType (MethodSignature signature) => "delegate*";
+}