Skip to content

Commit 12dd07b

Browse files
[TrimmableTypeMap] Add ManifestGenerator (#10991)
* Add ManifestGenerator: Cecil-free manifest generation (#10807) ManifestGenerator in Microsoft.Android.Sdk.TrimmableTypeMap assembly. Converts ComponentInfo records from JavaPeerScanner into AndroidManifest.xml. - Data-driven property mapping (7 static arrays, 9 enum converters) - MainLauncher intent-filter, runtime provider, template merging, deduplication - Assembly-level: Permission, UsesPermission, UsesFeature, UsesLibrary, UsesConfiguration, Application, MetaData, Property - ManifestPlaceholders, debuggable/extractNativeLibs, ApplicationJavaClass - XA4213 constructor validation, duplicate Application detection - VersionCode defaults to '1' matching legacy 23 unit tests (xunit). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * trigger PR creation * Add ManifestGenerator: Cecil-free manifest generation (#10807) Split into focused classes in Microsoft.Android.Sdk.TrimmableTypeMap: - ManifestGenerator: orchestration (load template, call builders, write output) - ManifestConstants: shared AndroidNs and AttName - PropertyMapper: data-driven property mapping (7 arrays, MappingKind enum) - AndroidEnumConverter: 9 enum-to-string converters - ComponentElementBuilder: Activity/Service/Receiver/Provider/Instrumentation XML - AssemblyLevelElementBuilder: permissions, uses-permissions, features, libraries Features: MainLauncher, runtime provider (with dedup), template merging, ManifestPlaceholders, debuggable/extractNativeLibs, XA4213 validation. 30 unit tests (xunit, 130ms). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use [] instead of Array.Empty<T>() in ManifestModel Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 529cf7d commit 12dd07b

9 files changed

Lines changed: 1713 additions & 0 deletions

File tree

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#nullable enable
2+
3+
using System.Collections.Generic;
4+
5+
namespace Microsoft.Android.Sdk.TrimmableTypeMap;
6+
7+
/// <summary>
8+
/// Converts Android enum integer values to their XML attribute string representations.
9+
/// Ported from ManifestDocumentElement.cs.
10+
/// </summary>
11+
static class AndroidEnumConverter
12+
{
13+
public static string? LaunchModeToString (int value) => value switch {
14+
1 => "singleTop",
15+
2 => "singleTask",
16+
3 => "singleInstance",
17+
4 => "singleInstancePerTask",
18+
_ => null,
19+
};
20+
21+
public static string? ScreenOrientationToString (int value) => value switch {
22+
0 => "landscape",
23+
1 => "portrait",
24+
3 => "sensor",
25+
4 => "nosensor",
26+
5 => "user",
27+
6 => "behind",
28+
7 => "reverseLandscape",
29+
8 => "reversePortrait",
30+
9 => "sensorLandscape",
31+
10 => "sensorPortrait",
32+
11 => "fullSensor",
33+
12 => "userLandscape",
34+
13 => "userPortrait",
35+
14 => "fullUser",
36+
15 => "locked",
37+
-1 => "unspecified",
38+
_ => null,
39+
};
40+
41+
public static string? ConfigChangesToString (int value)
42+
{
43+
var parts = new List<string> ();
44+
if ((value & 0x0001) != 0) parts.Add ("mcc");
45+
if ((value & 0x0002) != 0) parts.Add ("mnc");
46+
if ((value & 0x0004) != 0) parts.Add ("locale");
47+
if ((value & 0x0008) != 0) parts.Add ("touchscreen");
48+
if ((value & 0x0010) != 0) parts.Add ("keyboard");
49+
if ((value & 0x0020) != 0) parts.Add ("keyboardHidden");
50+
if ((value & 0x0040) != 0) parts.Add ("navigation");
51+
if ((value & 0x0080) != 0) parts.Add ("orientation");
52+
if ((value & 0x0100) != 0) parts.Add ("screenLayout");
53+
if ((value & 0x0200) != 0) parts.Add ("uiMode");
54+
if ((value & 0x0400) != 0) parts.Add ("screenSize");
55+
if ((value & 0x0800) != 0) parts.Add ("smallestScreenSize");
56+
if ((value & 0x1000) != 0) parts.Add ("density");
57+
if ((value & 0x2000) != 0) parts.Add ("layoutDirection");
58+
if ((value & 0x4000) != 0) parts.Add ("colorMode");
59+
if ((value & 0x8000) != 0) parts.Add ("grammaticalGender");
60+
if ((value & 0x10000000) != 0) parts.Add ("fontWeightAdjustment");
61+
if ((value & 0x40000000) != 0) parts.Add ("fontScale");
62+
return parts.Count > 0 ? string.Join ("|", parts) : null;
63+
}
64+
65+
public static string? SoftInputToString (int value)
66+
{
67+
var parts = new List<string> ();
68+
int state = value & 0x0f;
69+
int adjust = value & 0xf0;
70+
if (state == 1) parts.Add ("stateUnchanged");
71+
else if (state == 2) parts.Add ("stateHidden");
72+
else if (state == 3) parts.Add ("stateAlwaysHidden");
73+
else if (state == 4) parts.Add ("stateVisible");
74+
else if (state == 5) parts.Add ("stateAlwaysVisible");
75+
if (adjust == 0x10) parts.Add ("adjustResize");
76+
else if (adjust == 0x20) parts.Add ("adjustPan");
77+
else if (adjust == 0x30) parts.Add ("adjustNothing");
78+
return parts.Count > 0 ? string.Join ("|", parts) : null;
79+
}
80+
81+
public static string? DocumentLaunchModeToString (int value) => value switch {
82+
1 => "intoExisting",
83+
2 => "always",
84+
3 => "never",
85+
_ => null,
86+
};
87+
88+
public static string? UiOptionsToString (int value) => value switch {
89+
1 => "splitActionBarWhenNarrow",
90+
_ => null,
91+
};
92+
93+
public static string? ForegroundServiceTypeToString (int value)
94+
{
95+
var parts = new List<string> ();
96+
if ((value & 0x00000001) != 0) parts.Add ("dataSync");
97+
if ((value & 0x00000002) != 0) parts.Add ("mediaPlayback");
98+
if ((value & 0x00000004) != 0) parts.Add ("phoneCall");
99+
if ((value & 0x00000008) != 0) parts.Add ("location");
100+
if ((value & 0x00000010) != 0) parts.Add ("connectedDevice");
101+
if ((value & 0x00000020) != 0) parts.Add ("mediaProjection");
102+
if ((value & 0x00000040) != 0) parts.Add ("camera");
103+
if ((value & 0x00000080) != 0) parts.Add ("microphone");
104+
if ((value & 0x00000100) != 0) parts.Add ("health");
105+
if ((value & 0x00000200) != 0) parts.Add ("remoteMessaging");
106+
if ((value & 0x00000400) != 0) parts.Add ("systemExempted");
107+
if ((value & 0x00000800) != 0) parts.Add ("shortService");
108+
if ((value & 0x40000000) != 0) parts.Add ("specialUse");
109+
return parts.Count > 0 ? string.Join ("|", parts) : null;
110+
}
111+
112+
public static string? ProtectionToString (int value)
113+
{
114+
int baseValue = value & 0x0f;
115+
return baseValue switch {
116+
0 => "normal",
117+
1 => "dangerous",
118+
2 => "signature",
119+
3 => "signatureOrSystem",
120+
_ => null,
121+
};
122+
}
123+
124+
public static string? ActivityPersistableModeToString (int value) => value switch {
125+
0 => "persistRootOnly",
126+
1 => "persistAcrossReboots",
127+
2 => "persistNever",
128+
_ => null,
129+
};
130+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#nullable enable
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Globalization;
6+
using System.Linq;
7+
using System.Xml.Linq;
8+
9+
namespace Microsoft.Android.Sdk.TrimmableTypeMap;
10+
11+
/// <summary>
12+
/// Adds assembly-level manifest elements (permissions, uses-permissions, uses-features,
13+
/// uses-library, uses-configuration, meta-data, property).
14+
/// </summary>
15+
static class AssemblyLevelElementBuilder
16+
{
17+
static readonly XNamespace AndroidNs = ManifestConstants.AndroidNs;
18+
static readonly XName AttName = ManifestConstants.AttName;
19+
20+
internal static void AddAssemblyLevelElements (XElement manifest, XElement app, AssemblyManifestInfo info)
21+
{
22+
var existingPermissions = new HashSet<string> (
23+
manifest.Elements ("permission").Select (e => (string?)e.Attribute (AttName)).OfType<string> ());
24+
var existingUsesPermissions = new HashSet<string> (
25+
manifest.Elements ("uses-permission").Select (e => (string?)e.Attribute (AttName)).OfType<string> ());
26+
27+
// <permission> elements
28+
foreach (var perm in info.Permissions) {
29+
if (string.IsNullOrEmpty (perm.Name) || existingPermissions.Contains (perm.Name)) {
30+
continue;
31+
}
32+
var element = new XElement ("permission", new XAttribute (AttName, perm.Name));
33+
PropertyMapper.MapDictionaryProperties (element, perm.Properties, "Label", "label");
34+
PropertyMapper.MapDictionaryProperties (element, perm.Properties, "Description", "description");
35+
PropertyMapper.MapDictionaryProperties (element, perm.Properties, "Icon", "icon");
36+
PropertyMapper.MapDictionaryProperties (element, perm.Properties, "PermissionGroup", "permissionGroup");
37+
PropertyMapper.MapDictionaryEnumProperty (element, perm.Properties, "ProtectionLevel", "protectionLevel", AndroidEnumConverter.ProtectionToString);
38+
manifest.Add (element);
39+
}
40+
41+
// <permission-group> elements
42+
foreach (var pg in info.PermissionGroups) {
43+
if (string.IsNullOrEmpty (pg.Name)) {
44+
continue;
45+
}
46+
var element = new XElement ("permission-group", new XAttribute (AttName, pg.Name));
47+
PropertyMapper.MapDictionaryProperties (element, pg.Properties, "Label", "label");
48+
PropertyMapper.MapDictionaryProperties (element, pg.Properties, "Description", "description");
49+
PropertyMapper.MapDictionaryProperties (element, pg.Properties, "Icon", "icon");
50+
manifest.Add (element);
51+
}
52+
53+
// <permission-tree> elements
54+
foreach (var pt in info.PermissionTrees) {
55+
if (string.IsNullOrEmpty (pt.Name)) {
56+
continue;
57+
}
58+
var element = new XElement ("permission-tree", new XAttribute (AttName, pt.Name));
59+
PropertyMapper.MapDictionaryProperties (element, pt.Properties, "Label", "label");
60+
PropertyMapper.MapDictionaryProperties (element, pt.Properties, "Icon", "icon");
61+
manifest.Add (element);
62+
}
63+
64+
// <uses-permission> elements
65+
foreach (var up in info.UsesPermissions) {
66+
if (string.IsNullOrEmpty (up.Name) || existingUsesPermissions.Contains (up.Name)) {
67+
continue;
68+
}
69+
var element = new XElement ("uses-permission", new XAttribute (AttName, up.Name));
70+
if (up.MaxSdkVersion.HasValue) {
71+
element.SetAttributeValue (AndroidNs + "maxSdkVersion", up.MaxSdkVersion.Value.ToString (CultureInfo.InvariantCulture));
72+
}
73+
manifest.Add (element);
74+
}
75+
76+
// <uses-feature> elements
77+
var existingFeatures = new HashSet<string> (
78+
manifest.Elements ("uses-feature").Select (e => (string?)e.Attribute (AttName)).OfType<string> ());
79+
foreach (var uf in info.UsesFeatures) {
80+
if (uf.Name is not null && !existingFeatures.Contains (uf.Name)) {
81+
var element = new XElement ("uses-feature",
82+
new XAttribute (AttName, uf.Name),
83+
new XAttribute (AndroidNs + "required", uf.Required ? "true" : "false"));
84+
manifest.Add (element);
85+
} else if (uf.GLESVersion != 0) {
86+
var versionStr = $"0x{uf.GLESVersion:X8}";
87+
if (!manifest.Elements ("uses-feature").Any (e => (string?)e.Attribute (AndroidNs + "glEsVersion") == versionStr)) {
88+
var element = new XElement ("uses-feature",
89+
new XAttribute (AndroidNs + "glEsVersion", versionStr),
90+
new XAttribute (AndroidNs + "required", uf.Required ? "true" : "false"));
91+
manifest.Add (element);
92+
}
93+
}
94+
}
95+
96+
// <uses-library> elements inside <application>
97+
foreach (var ul in info.UsesLibraries) {
98+
if (string.IsNullOrEmpty (ul.Name)) {
99+
continue;
100+
}
101+
if (!app.Elements ("uses-library").Any (e => (string?)e.Attribute (AttName) == ul.Name)) {
102+
app.Add (new XElement ("uses-library",
103+
new XAttribute (AttName, ul.Name),
104+
new XAttribute (AndroidNs + "required", ul.Required ? "true" : "false")));
105+
}
106+
}
107+
108+
// Assembly-level <meta-data> inside <application>
109+
foreach (var md in info.MetaData) {
110+
if (string.IsNullOrEmpty (md.Name)) {
111+
continue;
112+
}
113+
if (!app.Elements ("meta-data").Any (e => (string?)e.Attribute (AndroidNs + "name") == md.Name)) {
114+
app.Add (ComponentElementBuilder.CreateMetaDataElement (md));
115+
}
116+
}
117+
118+
// Assembly-level <property> inside <application>
119+
foreach (var prop in info.Properties) {
120+
if (string.IsNullOrEmpty (prop.Name)) {
121+
continue;
122+
}
123+
if (!app.Elements ("property").Any (e => (string?)e.Attribute (AndroidNs + "name") == prop.Name)) {
124+
var element = new XElement ("property",
125+
new XAttribute (AndroidNs + "name", prop.Name));
126+
if (prop.Value is not null) {
127+
element.SetAttributeValue (AndroidNs + "value", prop.Value);
128+
}
129+
if (prop.Resource is not null) {
130+
element.SetAttributeValue (AndroidNs + "resource", prop.Resource);
131+
}
132+
app.Add (element);
133+
}
134+
}
135+
136+
// <uses-configuration> elements
137+
foreach (var uc in info.UsesConfigurations) {
138+
var element = new XElement ("uses-configuration");
139+
if (uc.ReqFiveWayNav) {
140+
element.SetAttributeValue (AndroidNs + "reqFiveWayNav", "true");
141+
}
142+
if (uc.ReqHardKeyboard) {
143+
element.SetAttributeValue (AndroidNs + "reqHardKeyboard", "true");
144+
}
145+
if (uc.ReqKeyboardType is not null) {
146+
element.SetAttributeValue (AndroidNs + "reqKeyboardType", uc.ReqKeyboardType);
147+
}
148+
if (uc.ReqNavigation is not null) {
149+
element.SetAttributeValue (AndroidNs + "reqNavigation", uc.ReqNavigation);
150+
}
151+
if (uc.ReqTouchScreen is not null) {
152+
element.SetAttributeValue (AndroidNs + "reqTouchScreen", uc.ReqTouchScreen);
153+
}
154+
manifest.Add (element);
155+
}
156+
}
157+
158+
internal static void ApplyApplicationProperties (XElement app, Dictionary<string, object?> properties)
159+
{
160+
PropertyMapper.ApplyMappings (app, properties, PropertyMapper.ApplicationPropertyMappings, skipExisting: true);
161+
}
162+
163+
internal static void AddInternetPermission (XElement manifest)
164+
{
165+
if (!manifest.Elements ("uses-permission").Any (p =>
166+
(string?)p.Attribute (AndroidNs + "name") == "android.permission.INTERNET")) {
167+
manifest.Add (new XElement ("uses-permission",
168+
new XAttribute (AndroidNs + "name", "android.permission.INTERNET")));
169+
}
170+
}
171+
}

0 commit comments

Comments
 (0)