@@ -98,14 +98,33 @@ void Build ()
9898 // [Export] is a method-level attribute; it is parsed at scan time by JavaPeerScanner
9999 } else if ( IsKnownComponentAttribute ( attrName ) ) {
100100 attrInfo ??= CreateTypeAttributeInfo ( attrName ) ;
101- var name = TryGetNameProperty ( ca ) ;
101+ var value = DecodeAttribute ( ca ) ;
102+
103+ // Capture all named properties
104+ foreach ( var named in value . NamedArguments ) {
105+ if ( named . Name is not null ) {
106+ attrInfo . Properties [ named . Name ] = named . Value ;
107+ }
108+ }
109+
110+ var name = TryGetNameFromDecodedAttribute ( value ) ;
102111 if ( name is not null ) {
103112 attrInfo . JniName = name . Replace ( '.' , '/' ) ;
104113 }
105114 if ( attrInfo is ApplicationAttributeInfo applicationAttributeInfo ) {
106- applicationAttributeInfo . BackupAgent = TryGetTypeProperty ( ca , "BackupAgent" ) ;
107- applicationAttributeInfo . ManageSpaceActivity = TryGetTypeProperty ( ca , "ManageSpaceActivity" ) ;
115+ if ( TryGetNamedArgument < string > ( value , "BackupAgent" , out var backupAgent ) ) {
116+ applicationAttributeInfo . BackupAgent = backupAgent ;
117+ }
118+ if ( TryGetNamedArgument < string > ( value , "ManageSpaceActivity" , out var manageSpace ) ) {
119+ applicationAttributeInfo . ManageSpaceActivity = manageSpace ;
120+ }
108121 }
122+ } else if ( attrName == "IntentFilterAttribute" ) {
123+ attrInfo ??= new TypeAttributeInfo ( "IntentFilterAttribute" ) ;
124+ attrInfo . IntentFilters . Add ( ParseIntentFilterAttribute ( ca ) ) ;
125+ } else if ( attrName == "MetaDataAttribute" ) {
126+ attrInfo ??= new TypeAttributeInfo ( "MetaDataAttribute" ) ;
127+ attrInfo . MetaData . Add ( ParseMetaDataAttribute ( ca ) ) ;
109128 } else if ( attrInfo is null && ImplementsJniNameProviderAttribute ( ca ) ) {
110129 // Custom attribute implementing IJniNameProviderAttribute (e.g., user-defined [CustomJniName])
111130 var name = TryGetNameProperty ( ca ) ;
@@ -239,6 +258,14 @@ RegisterInfo ParseRegisterInfo (CustomAttributeValue<string> value)
239258 }
240259
241260 var value = DecodeAttribute ( ca ) ;
261+ return TryGetNameFromDecodedAttribute ( value ) ;
262+ }
263+
264+ static string ? TryGetNameFromDecodedAttribute ( CustomAttributeValue < string > value )
265+ {
266+ if ( TryGetNamedArgument < string > ( value , "Name" , out var name ) && ! string . IsNullOrEmpty ( name ) ) {
267+ return name ;
268+ }
242269
243270 // Fall back to first constructor argument (e.g., [CustomJniName("...")])
244271 if ( value . FixedArguments . Length > 0 && value . FixedArguments [ 0 ] . Value is string ctorName && ! string . IsNullOrEmpty ( ctorName ) ) {
@@ -248,6 +275,69 @@ RegisterInfo ParseRegisterInfo (CustomAttributeValue<string> value)
248275 return null ;
249276 }
250277
278+ IntentFilterInfo ParseIntentFilterAttribute ( CustomAttribute ca )
279+ {
280+ var value = DecodeAttribute ( ca ) ;
281+
282+ // First ctor argument is string[] actions
283+ var actions = new List < string > ( ) ;
284+ if ( value . FixedArguments . Length > 0 && value . FixedArguments [ 0 ] . Value is IReadOnlyCollection < CustomAttributeTypedArgument < string > > actionArgs ) {
285+ foreach ( var arg in actionArgs ) {
286+ if ( arg . Value is string action ) {
287+ actions . Add ( action ) ;
288+ }
289+ }
290+ }
291+
292+ var categories = new List < string > ( ) ;
293+ if ( TryGetNamedArgument < string > ( value , "Categories" , out _ ) ) {
294+ // Categories is a string[] property — decode it from the raw named argument
295+ foreach ( var named in value . NamedArguments ) {
296+ if ( named . Name == "Categories" && named . Value is IReadOnlyCollection < CustomAttributeTypedArgument < string > > catArgs ) {
297+ foreach ( var arg in catArgs ) {
298+ if ( arg . Value is string cat ) {
299+ categories . Add ( cat ) ;
300+ }
301+ }
302+ }
303+ }
304+ }
305+
306+ var properties = new Dictionary < string , object ? > ( StringComparer . Ordinal ) ;
307+ foreach ( var named in value . NamedArguments ) {
308+ if ( named . Name is not null && named . Name != "Categories" ) {
309+ properties [ named . Name ] = named . Value ;
310+ }
311+ }
312+
313+ return new IntentFilterInfo {
314+ Actions = actions ,
315+ Categories = categories ,
316+ Properties = properties ,
317+ } ;
318+ }
319+
320+ MetaDataInfo ParseMetaDataAttribute ( CustomAttribute ca )
321+ {
322+ var value = DecodeAttribute ( ca ) ;
323+
324+ string name = "" ;
325+ if ( value . FixedArguments . Length > 0 && value . FixedArguments [ 0 ] . Value is string nameArg ) {
326+ name = nameArg ;
327+ }
328+
329+ string ? metaValue = null ;
330+ string ? resource = null ;
331+ TryGetNamedArgument < string > ( value , "Value" , out metaValue ) ;
332+ TryGetNamedArgument < string > ( value , "Resource" , out resource ) ;
333+
334+ return new MetaDataInfo {
335+ Name = name ,
336+ Value = metaValue ,
337+ Resource = resource ,
338+ } ;
339+ }
340+
251341 static bool TryGetNamedArgument < T > ( CustomAttributeValue < string > value , string argumentName , [ MaybeNullWhen ( false ) ] out T argumentValue ) where T : notnull
252342 {
253343 foreach ( var named in value . NamedArguments ) {
@@ -260,6 +350,193 @@ static bool TryGetNamedArgument<T> (CustomAttributeValue<string> value, string a
260350 return false ;
261351 }
262352
353+ /// <summary>
354+ /// Scans assembly-level custom attributes for manifest-related data.
355+ /// </summary>
356+ internal void ScanAssemblyAttributes ( AssemblyManifestInfo info )
357+ {
358+ var asmDef = Reader . GetAssemblyDefinition ( ) ;
359+ foreach ( var caHandle in asmDef . GetCustomAttributes ( ) ) {
360+ var ca = Reader . GetCustomAttribute ( caHandle ) ;
361+ var attrName = GetCustomAttributeName ( ca , Reader ) ;
362+ if ( attrName is null ) {
363+ continue ;
364+ }
365+
366+ switch ( attrName ) {
367+ case "PermissionAttribute" :
368+ info . Permissions . Add ( ParsePermissionAttribute ( ca ) ) ;
369+ break ;
370+ case "PermissionGroupAttribute" :
371+ info . PermissionGroups . Add ( ParsePermissionGroupAttribute ( ca ) ) ;
372+ break ;
373+ case "PermissionTreeAttribute" :
374+ info . PermissionTrees . Add ( ParsePermissionTreeAttribute ( ca ) ) ;
375+ break ;
376+ case "UsesPermissionAttribute" :
377+ info . UsesPermissions . Add ( ParseUsesPermissionAttribute ( ca ) ) ;
378+ break ;
379+ case "UsesFeatureAttribute" :
380+ info . UsesFeatures . Add ( ParseUsesFeatureAttribute ( ca ) ) ;
381+ break ;
382+ case "UsesLibraryAttribute" :
383+ info . UsesLibraries . Add ( ParseUsesLibraryAttribute ( ca ) ) ;
384+ break ;
385+ case "UsesConfigurationAttribute" :
386+ info . UsesConfigurations . Add ( ParseUsesConfigurationAttribute ( ca ) ) ;
387+ break ;
388+ case "MetaDataAttribute" :
389+ info . MetaData . Add ( ParseMetaDataAttribute ( ca ) ) ;
390+ break ;
391+ case "PropertyAttribute" :
392+ info . Properties . Add ( ParsePropertyAttribute ( ca ) ) ;
393+ break ;
394+ case "ApplicationAttribute" :
395+ info . ApplicationProperties ??= new Dictionary < string , object ? > ( StringComparer . Ordinal ) ;
396+ var appValue = DecodeAttribute ( ca ) ;
397+ foreach ( var named in appValue . NamedArguments ) {
398+ if ( named . Name is not null ) {
399+ info . ApplicationProperties [ named . Name ] = named . Value ;
400+ }
401+ }
402+ break ;
403+ }
404+ }
405+ }
406+
407+ ( string name , Dictionary < string , object ? > props ) ParseNameAndProperties ( CustomAttribute ca )
408+ {
409+ var value = DecodeAttribute ( ca ) ;
410+ string name = "" ;
411+ var props = new Dictionary < string , object ? > ( StringComparer . Ordinal ) ;
412+ foreach ( var named in value . NamedArguments ) {
413+ if ( named . Name == "Name" && named . Value is string n ) {
414+ name = n ;
415+ }
416+ if ( named . Name is not null ) {
417+ props [ named . Name ] = named . Value ;
418+ }
419+ }
420+ return ( name , props ) ;
421+ }
422+
423+ PermissionInfo ParsePermissionAttribute ( CustomAttribute ca )
424+ {
425+ var ( name , props ) = ParseNameAndProperties ( ca ) ;
426+ return new PermissionInfo { Name = name , Properties = props } ;
427+ }
428+
429+ PermissionGroupInfo ParsePermissionGroupAttribute ( CustomAttribute ca )
430+ {
431+ var ( name , props ) = ParseNameAndProperties ( ca ) ;
432+ return new PermissionGroupInfo { Name = name , Properties = props } ;
433+ }
434+
435+ PermissionTreeInfo ParsePermissionTreeAttribute ( CustomAttribute ca )
436+ {
437+ var ( name , props ) = ParseNameAndProperties ( ca ) ;
438+ return new PermissionTreeInfo { Name = name , Properties = props } ;
439+ }
440+
441+ UsesPermissionInfo ParseUsesPermissionAttribute ( CustomAttribute ca )
442+ {
443+ var value = DecodeAttribute ( ca ) ;
444+ string name = "" ;
445+ int ? maxSdk = null ;
446+ if ( value . FixedArguments . Length > 0 && value . FixedArguments [ 0 ] . Value is string n ) {
447+ name = n ;
448+ }
449+ foreach ( var named in value . NamedArguments ) {
450+ if ( named . Name == "Name" && named . Value is string nameVal ) {
451+ name = nameVal ;
452+ } else if ( named . Name == "MaxSdkVersion" && named . Value is int max ) {
453+ maxSdk = max ;
454+ }
455+ }
456+ return new UsesPermissionInfo { Name = name , MaxSdkVersion = maxSdk } ;
457+ }
458+
459+ UsesFeatureInfo ParseUsesFeatureAttribute ( CustomAttribute ca )
460+ {
461+ var value = DecodeAttribute ( ca ) ;
462+ string ? name = null ;
463+ int glesVersion = 0 ;
464+ bool required = true ;
465+ foreach ( var named in value . NamedArguments ) {
466+ if ( named . Name == "Name" && named . Value is string n ) {
467+ name = n ;
468+ } else if ( named . Name == "GLESVersion" && named . Value is int v ) {
469+ glesVersion = v ;
470+ } else if ( named . Name == "Required" && named . Value is bool r ) {
471+ required = r ;
472+ }
473+ }
474+ return new UsesFeatureInfo { Name = name , GLESVersion = glesVersion , Required = required } ;
475+ }
476+
477+ UsesLibraryInfo ParseUsesLibraryAttribute ( CustomAttribute ca )
478+ {
479+ var value = DecodeAttribute ( ca ) ;
480+ string name = "" ;
481+ bool required = true ;
482+ if ( value . FixedArguments . Length > 0 && value . FixedArguments [ 0 ] . Value is string n ) {
483+ name = n ;
484+ }
485+ foreach ( var named in value . NamedArguments ) {
486+ if ( named . Name == "Name" && named . Value is string nameVal ) {
487+ name = nameVal ;
488+ } else if ( named . Name == "Required" && named . Value is bool r ) {
489+ required = r ;
490+ }
491+ }
492+ return new UsesLibraryInfo { Name = name , Required = required } ;
493+ }
494+
495+ UsesConfigurationInfo ParseUsesConfigurationAttribute ( CustomAttribute ca )
496+ {
497+ var value = DecodeAttribute ( ca ) ;
498+ bool reqFiveWayNav = false ;
499+ bool reqHardKeyboard = false ;
500+ string ? reqKeyboardType = null ;
501+ string ? reqNavigation = null ;
502+ string ? reqTouchScreen = null ;
503+ foreach ( var named in value . NamedArguments ) {
504+ switch ( named . Name ) {
505+ case "ReqFiveWayNav" when named . Value is bool b : reqFiveWayNav = b ; break ;
506+ case "ReqHardKeyboard" when named . Value is bool b : reqHardKeyboard = b ; break ;
507+ case "ReqKeyboardType" when named . Value is string s : reqKeyboardType = s ; break ;
508+ case "ReqNavigation" when named . Value is string s : reqNavigation = s ; break ;
509+ case "ReqTouchScreen" when named . Value is string s : reqTouchScreen = s ; break ;
510+ }
511+ }
512+ return new UsesConfigurationInfo {
513+ ReqFiveWayNav = reqFiveWayNav ,
514+ ReqHardKeyboard = reqHardKeyboard ,
515+ ReqKeyboardType = reqKeyboardType ,
516+ ReqNavigation = reqNavigation ,
517+ ReqTouchScreen = reqTouchScreen ,
518+ } ;
519+ }
520+
521+ PropertyInfo ParsePropertyAttribute ( CustomAttribute ca )
522+ {
523+ var value = DecodeAttribute ( ca ) ;
524+ string name = "" ;
525+ string ? propValue = null ;
526+ string ? resource = null ;
527+ if ( value . FixedArguments . Length > 0 && value . FixedArguments [ 0 ] . Value is string n ) {
528+ name = n ;
529+ }
530+ foreach ( var named in value . NamedArguments ) {
531+ switch ( named . Name ) {
532+ case "Name" when named . Value is string s : name = s ; break ;
533+ case "Value" when named . Value is string s : propValue = s ; break ;
534+ case "Resource" when named . Value is string s : resource = s ; break ;
535+ }
536+ }
537+ return new PropertyInfo { Name = name , Value = propValue , Resource = resource } ;
538+ }
539+
263540 public void Dispose ( )
264541 {
265542 peReader . Dispose ( ) ;
@@ -290,6 +567,21 @@ class TypeAttributeInfo (string attributeName)
290567{
291568 public string AttributeName { get ; } = attributeName ;
292569 public string ? JniName { get ; set ; }
570+
571+ /// <summary>
572+ /// All named property values from the component attribute.
573+ /// </summary>
574+ public Dictionary < string , object ? > Properties { get ; } = new ( StringComparer . Ordinal ) ;
575+
576+ /// <summary>
577+ /// Intent filters declared on this type via [IntentFilter] attributes.
578+ /// </summary>
579+ public List < IntentFilterInfo > IntentFilters { get ; } = [ ] ;
580+
581+ /// <summary>
582+ /// Metadata entries declared on this type via [MetaData] attributes.
583+ /// </summary>
584+ public List < MetaDataInfo > MetaData { get ; } = [ ] ;
293585}
294586
295587sealed class ApplicationAttributeInfo ( ) : TypeAttributeInfo ( "ApplicationAttribute" )
0 commit comments