99using System . Threading ;
1010using Android . Runtime ;
1111using Java . Interop ;
12+ using Java . Interop . Tools . TypeNameMappings ;
1213
1314namespace Microsoft . Android . Runtime ;
1415
@@ -29,6 +30,8 @@ class TrimmableTypeMap
2930 readonly IReadOnlyDictionary < string , Type > _typeMap ;
3031 readonly IReadOnlyDictionary < Type , Type > _proxyTypeMap ;
3132 readonly ConcurrentDictionary < Type , JavaPeerProxy ? > _proxyCache = new ( ) ;
33+ readonly ConcurrentDictionary < Type , string ? > _jniNameCache = new ( ) ;
34+ readonly ConcurrentDictionary < string , JavaPeerProxy ? > _peerProxyCache = new ( StringComparer . Ordinal ) ;
3235
3336 TrimmableTypeMap ( )
3437 {
@@ -69,7 +72,19 @@ unsafe void RegisterNatives ()
6972 }
7073
7174 internal bool TryGetType ( string jniSimpleReference , [ NotNullWhen ( true ) ] out Type ? type )
72- => _typeMap . TryGetValue ( jniSimpleReference , out type ) ;
75+ {
76+ if ( ! _typeMap . TryGetValue ( jniSimpleReference , out var mappedType ) ) {
77+ type = null ;
78+ return false ;
79+ }
80+
81+ // External typemap entries usually point at the generated proxy for ACW-backed types.
82+ // Surface the real managed peer when possible so activation and virtual dispatch land
83+ // on the user's override instead of the generated proxy type.
84+ var proxy = mappedType . GetCustomAttribute < JavaPeerProxy > ( inherit : false ) ;
85+ type = proxy ? . TargetType ?? mappedType ;
86+ return true ;
87+ }
7388
7489 /// <summary>
7590 /// Finds the proxy for a managed type using the generated proxy type map.
@@ -87,30 +102,170 @@ internal bool TryGetType (string jniSimpleReference, [NotNullWhen (true)] out Ty
87102 return proxyType . GetCustomAttribute < JavaPeerProxy > ( inherit : false ) ;
88103 }
89104
90- internal bool TryGetJniNameForManagedType ( Type managedType , [ NotNullWhen ( true ) ] out string ? jniName )
105+ internal bool TryGetJniName ( Type type , [ NotNullWhen ( true ) ] out string ? jniName )
91106 {
92- var proxy = GetProxyForManagedType ( managedType ) ;
107+ if ( _jniNameCache . TryGetValue ( type , out jniName ) ) {
108+ return jniName != null ;
109+ }
110+
111+ var proxy = GetProxyForManagedType ( type ) ;
93112 if ( proxy is not null ) {
94113 jniName = proxy . JniName ;
114+ _jniNameCache [ type ] = jniName ;
115+ return true ;
116+ }
117+
118+ if ( TryGetJniNameForType ( type , out jniName ) ) {
119+ _jniNameCache [ type ] = jniName ;
95120 return true ;
96121 }
97122
123+ if ( TryGetCompatJniNameForAndroidComponent ( type , out jniName ) ) {
124+ _jniNameCache [ type ] = jniName ;
125+ return true ;
126+ }
127+
128+ // Prefer the JavaNativeTypeManager calculation for user/application types,
129+ // as it matches the ACW generation rules used during the build.
130+ if ( typeof ( IJavaPeerable ) . IsAssignableFrom ( type ) ) {
131+ jniName = JavaNativeTypeManager . ToJniName ( type ) ;
132+ if ( ! string . IsNullOrEmpty ( jniName ) && jniName != "java/lang/Object" ) {
133+ _jniNameCache [ type ] = jniName ;
134+ return true ;
135+ }
136+ }
137+
98138 jniName = null ;
139+ _jniNameCache [ type ] = null ;
99140 return false ;
100141 }
101142
143+ internal bool TryGetJniNameForManagedType ( Type managedType , [ NotNullWhen ( true ) ] out string ? jniName )
144+ => TryGetJniName ( managedType , out jniName ) ;
145+
146+ internal JavaPeerProxy ? GetProxyForPeer ( IntPtr handle , Type ? targetType = null )
147+ {
148+ if ( handle == IntPtr . Zero ) {
149+ return null ;
150+ }
151+
152+ var selfRef = new JniObjectReference ( handle ) ;
153+ var jniClass = JniEnvironment . Types . GetObjectClass ( selfRef ) ;
154+
155+ try {
156+ while ( jniClass . IsValid ) {
157+ var className = JniEnvironment . Types . GetJniTypeNameFromClass ( jniClass ) ;
158+ if ( className != null ) {
159+ if ( _peerProxyCache . TryGetValue ( className , out var cached ) ) {
160+ if ( cached != null && ( targetType is null || targetType . IsAssignableFrom ( cached . TargetType ) ) ) {
161+ return cached ;
162+ }
163+ } else if ( _typeMap . TryGetValue ( className , out var mappedType ) ) {
164+ var proxy = mappedType . GetCustomAttribute < JavaPeerProxy > ( inherit : false ) ;
165+ _peerProxyCache [ className ] = proxy ;
166+ if ( proxy != null && ( targetType is null || targetType . IsAssignableFrom ( proxy . TargetType ) ) ) {
167+ return proxy ;
168+ }
169+ }
170+ }
171+
172+ var super = JniEnvironment . Types . GetSuperclass ( jniClass ) ;
173+ JniObjectReference . Dispose ( ref jniClass ) ;
174+ jniClass = super ;
175+ }
176+ } finally {
177+ JniObjectReference . Dispose ( ref jniClass ) ;
178+ }
179+
180+ return null ;
181+ }
182+
183+ internal IJavaPeerable ? CreatePeer ( IntPtr handle , JniHandleOwnership transfer , Type ? targetType = null )
184+ {
185+ var proxy = GetProxyForPeer ( handle , targetType ) ;
186+ if ( proxy is null && targetType is not null ) {
187+ proxy = GetProxyForManagedType ( targetType ) ;
188+ // Verify the Java object is actually assignable to the target Java type
189+ // before creating the peer. Without this, we'd create invalid peers
190+ // (e.g., IAppendableInvoker wrapping a java.lang.Integer).
191+ if ( proxy is not null && TryGetJniName ( targetType , out var targetJniName ) ) {
192+ var selfRef = new JniObjectReference ( handle ) ;
193+ var objClass = JniEnvironment . Types . GetObjectClass ( selfRef ) ;
194+ var targetClass = JniEnvironment . Types . FindClass ( targetJniName ) ;
195+ try {
196+ if ( ! JniEnvironment . Types . IsAssignableFrom ( objClass , targetClass ) ) {
197+ proxy = null ;
198+ }
199+ } finally {
200+ JniObjectReference . Dispose ( ref objClass ) ;
201+ JniObjectReference . Dispose ( ref targetClass ) ;
202+ }
203+ }
204+ }
205+
206+ return proxy ? . CreateInstance ( handle , transfer ) ;
207+ }
208+
209+ static bool TryGetCompatJniNameForAndroidComponent ( Type type , [ NotNullWhen ( true ) ] out string ? jniName )
210+ {
211+ if ( ! IsAndroidComponentType ( type ) ) {
212+ jniName = null ;
213+ return false ;
214+ }
215+
216+ var ( typeName , parentJniName , ns ) = GetCompatTypeNameParts ( type ) ;
217+ jniName = parentJniName is not null
218+ ? $ "{ parentJniName } _{ typeName } "
219+ : ns . Length == 0
220+ ? typeName
221+ : $ "{ ns . ToLowerInvariant ( ) . Replace ( '.' , '/' ) } /{ typeName } ";
222+ return true ;
223+ }
224+
225+ static bool IsAndroidComponentType ( Type type )
226+ {
227+ return type . IsDefined ( typeof ( global ::Android . App . ActivityAttribute ) , inherit : false ) ||
228+ type . IsDefined ( typeof ( global ::Android . App . ApplicationAttribute ) , inherit : false ) ||
229+ type . IsDefined ( typeof ( global ::Android . App . InstrumentationAttribute ) , inherit : false ) ||
230+ type . IsDefined ( typeof ( global ::Android . App . ServiceAttribute ) , inherit : false ) ||
231+ type . IsDefined ( typeof ( global ::Android . Content . BroadcastReceiverAttribute ) , inherit : false ) ||
232+ type . IsDefined ( typeof ( global ::Android . Content . ContentProviderAttribute ) , inherit : false ) ;
233+ }
234+
235+ static ( string TypeName , string ? ParentJniName , string Namespace ) GetCompatTypeNameParts ( Type type )
236+ {
237+ var nameParts = new List < string > { SanitizeTypeName ( type . Name ) } ;
238+ var current = type ;
239+ string ? parentJniName = null ;
240+
241+ while ( current . DeclaringType is Type parentType ) {
242+ if ( TryGetJniNameForType ( parentType , out var explicitJniName ) ||
243+ TryGetCompatJniNameForAndroidComponent ( parentType , out explicitJniName ) ) {
244+ parentJniName = explicitJniName ;
245+ break ;
246+ }
247+
248+ nameParts . Add ( SanitizeTypeName ( parentType . Name ) ) ;
249+ current = parentType ;
250+ }
251+
252+ nameParts . Reverse ( ) ;
253+ return ( string . Join ( "_" , nameParts ) , parentJniName , current . Namespace ?? "" ) ;
254+ }
255+
256+ static string SanitizeTypeName ( string name )
257+ {
258+ var tick = name . IndexOf ( '`' ) ;
259+ return ( tick >= 0 ? name . Substring ( 0 , tick ) : name ) . Replace ( '+' , '_' ) ;
260+ }
261+
102262 /// <summary>
103263 /// Creates a peer instance using the proxy's CreateInstance method.
104264 /// Given a managed type, resolves the JNI name, finds the proxy, and calls CreateInstance.
105265 /// </summary>
106266 internal bool TryCreatePeer ( Type type , IntPtr handle , JniHandleOwnership transfer )
107267 {
108- var proxy = GetProxyForManagedType ( type ) ;
109- if ( proxy is null ) {
110- return false ;
111- }
112-
113- return proxy . CreateInstance ( handle , transfer ) != null ;
268+ return CreatePeer ( handle , transfer , type ) != null ;
114269 }
115270
116271 const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes . PublicConstructors | DynamicallyAccessedMemberTypes . NonPublicConstructors ;
0 commit comments