Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
371cbdc
Add ManifestGenerator: Cecil-free manifest generation (#10807)
simonrozsival Mar 21, 2026
b2d88ee
trigger PR creation
simonrozsival Mar 21, 2026
683c0be
Add ManifestGenerator: Cecil-free manifest generation (#10807)
simonrozsival Mar 21, 2026
6492b9a
Use [] instead of Array.Empty<T>() in ManifestModel
simonrozsival Mar 24, 2026
1741ccc
[TrimmableTypeMap] Build pipeline: targets, scanner, task, assembly s…
simonrozsival Mar 21, 2026
0b2cf49
Fix build: add AcwMapDirectory, update ManifestGenerator reference
simonrozsival Mar 22, 2026
af0d532
Simplify HelloWorld: avoid JavaCast<T> (workaround for disposed peer …
simonrozsival Mar 22, 2026
7334b51
Restore FindViewById<Button> sample (CreatePeer fix works)
simonrozsival Mar 22, 2026
0ac74bf
Generate JCWs for framework Implementor types (mono/ prefix)
simonrozsival Mar 22, 2026
d486268
Remove JCW framework filter, fix native callback name tests
simonrozsival Mar 23, 2026
caeddc0
Fix framework Implementor JCW generation (javac + runtime)
simonrozsival Mar 23, 2026
f329bdb
Fix _RemoveRegisterAttribute for PublishTrimmed=true
simonrozsival Mar 23, 2026
8fbe42c
Make _RemoveRegisterAttribute an empty target for trimmable path
simonrozsival Mar 23, 2026
25ab2c1
Reset HelloWorld sample to base, keep trimmable typemap config
simonrozsival Mar 23, 2026
8eb9f55
Address Copilot review feedback
simonrozsival Mar 23, 2026
6cc2c4c
Address review: fix acw-map.txt and JCW filter comments
simonrozsival Mar 23, 2026
64fc167
Address review: fix Categories parsing, delete ManifestModel, documen…
simonrozsival Mar 23, 2026
23e706d
Fix: reset Java.Interop submodule to match base
simonrozsival Mar 23, 2026
b8462c8
Fix test: JniNameToJavaName now converts $ to . for inner classes
simonrozsival Mar 23, 2026
4b764bb
Set TrimmableTypeMap=false for MonoVM and NativeAOT builds
simonrozsival Mar 24, 2026
e93bc71
Fix tests: get_TargetType inherited from JavaPeerProxy<T>
simonrozsival Mar 24, 2026
5b102db
Fix: use IntermediateOutputPath for typemap output directory
simonrozsival Mar 25, 2026
d0e8167
Extend scanner integration tests with component, export, and implemen…
simonrozsival Mar 25, 2026
f19ee96
Add assembly manifest attribute comparison tests
simonrozsival Mar 25, 2026
ffa3e40
Address review: deepen comparison assertions
simonrozsival Mar 25, 2026
7b988f1
Add JavaFields and ThrownNames integration tests
simonrozsival Mar 25, 2026
acef6c8
Fix integration test failures
simonrozsival Mar 25, 2026
4144d8f
Add 6 new scanner comparison integration tests
simonrozsival Mar 25, 2026
e71d316
Raise minimum type count threshold from 3000 to 8000
simonrozsival Mar 25, 2026
14fc56d
Update minimum type count threshold to 8500
simonrozsival Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions samples/HelloWorld/HelloWorld/HelloWorld.DotNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<TargetFramework>$(DotNetAndroidTargetFramework)</TargetFramework>
<OutputType>Exe</OutputType>
<RootNamespace>HelloWorld</RootNamespace>
<_AndroidTypeMapImplementation>trimmable</_AndroidTypeMapImplementation>
<UseMonoRuntime>false</UseMonoRuntime>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\HelloLibrary\HelloLibrary.DotNet.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#nullable enable

using System.Collections.Generic;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

/// <summary>
/// Converts Android enum integer values to their XML attribute string representations.
/// Ported from ManifestDocumentElement.cs.
/// </summary>
static class AndroidEnumConverter
{
public static string? LaunchModeToString (int value) => value switch {
1 => "singleTop",
2 => "singleTask",
3 => "singleInstance",
4 => "singleInstancePerTask",
_ => null,
};

public static string? ScreenOrientationToString (int value) => value switch {
0 => "landscape",
1 => "portrait",
3 => "sensor",
4 => "nosensor",
5 => "user",
6 => "behind",
7 => "reverseLandscape",
8 => "reversePortrait",
9 => "sensorLandscape",
10 => "sensorPortrait",
11 => "fullSensor",
12 => "userLandscape",
13 => "userPortrait",
14 => "fullUser",
15 => "locked",
-1 => "unspecified",
_ => null,
};

public static string? ConfigChangesToString (int value)
{
var parts = new List<string> ();
if ((value & 0x0001) != 0) parts.Add ("mcc");
if ((value & 0x0002) != 0) parts.Add ("mnc");
if ((value & 0x0004) != 0) parts.Add ("locale");
if ((value & 0x0008) != 0) parts.Add ("touchscreen");
if ((value & 0x0010) != 0) parts.Add ("keyboard");
if ((value & 0x0020) != 0) parts.Add ("keyboardHidden");
if ((value & 0x0040) != 0) parts.Add ("navigation");
if ((value & 0x0080) != 0) parts.Add ("orientation");
if ((value & 0x0100) != 0) parts.Add ("screenLayout");
if ((value & 0x0200) != 0) parts.Add ("uiMode");
if ((value & 0x0400) != 0) parts.Add ("screenSize");
if ((value & 0x0800) != 0) parts.Add ("smallestScreenSize");
if ((value & 0x1000) != 0) parts.Add ("density");
if ((value & 0x2000) != 0) parts.Add ("layoutDirection");
if ((value & 0x4000) != 0) parts.Add ("colorMode");
if ((value & 0x8000) != 0) parts.Add ("grammaticalGender");
if ((value & 0x10000000) != 0) parts.Add ("fontWeightAdjustment");
if ((value & 0x40000000) != 0) parts.Add ("fontScale");
return parts.Count > 0 ? string.Join ("|", parts) : null;
}

public static string? SoftInputToString (int value)
{
var parts = new List<string> ();
int state = value & 0x0f;
int adjust = value & 0xf0;
if (state == 1) parts.Add ("stateUnchanged");
else if (state == 2) parts.Add ("stateHidden");
else if (state == 3) parts.Add ("stateAlwaysHidden");
else if (state == 4) parts.Add ("stateVisible");
else if (state == 5) parts.Add ("stateAlwaysVisible");
if (adjust == 0x10) parts.Add ("adjustResize");
else if (adjust == 0x20) parts.Add ("adjustPan");
else if (adjust == 0x30) parts.Add ("adjustNothing");
return parts.Count > 0 ? string.Join ("|", parts) : null;
}

public static string? DocumentLaunchModeToString (int value) => value switch {
1 => "intoExisting",
2 => "always",
3 => "never",
_ => null,
};

public static string? UiOptionsToString (int value) => value switch {
1 => "splitActionBarWhenNarrow",
_ => null,
};

public static string? ForegroundServiceTypeToString (int value)
{
var parts = new List<string> ();
if ((value & 0x00000001) != 0) parts.Add ("dataSync");
if ((value & 0x00000002) != 0) parts.Add ("mediaPlayback");
if ((value & 0x00000004) != 0) parts.Add ("phoneCall");
if ((value & 0x00000008) != 0) parts.Add ("location");
if ((value & 0x00000010) != 0) parts.Add ("connectedDevice");
if ((value & 0x00000020) != 0) parts.Add ("mediaProjection");
if ((value & 0x00000040) != 0) parts.Add ("camera");
if ((value & 0x00000080) != 0) parts.Add ("microphone");
if ((value & 0x00000100) != 0) parts.Add ("health");
if ((value & 0x00000200) != 0) parts.Add ("remoteMessaging");
if ((value & 0x00000400) != 0) parts.Add ("systemExempted");
if ((value & 0x00000800) != 0) parts.Add ("shortService");
if ((value & 0x40000000) != 0) parts.Add ("specialUse");
return parts.Count > 0 ? string.Join ("|", parts) : null;
}

public static string? ProtectionToString (int value)
{
int baseValue = value & 0x0f;
return baseValue switch {
0 => "normal",
1 => "dangerous",
2 => "signature",
3 => "signatureOrSystem",
_ => null,
};
}

public static string? ActivityPersistableModeToString (int value) => value switch {
0 => "persistRootOnly",
1 => "persistAcrossReboots",
2 => "persistNever",
_ => null,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

/// <summary>
/// Adds assembly-level manifest elements (permissions, uses-permissions, uses-features,
/// uses-library, uses-configuration, meta-data, property).
/// </summary>
static class AssemblyLevelElementBuilder
{
static readonly XNamespace AndroidNs = ManifestConstants.AndroidNs;
static readonly XName AttName = ManifestConstants.AttName;

internal static void AddAssemblyLevelElements (XElement manifest, XElement app, AssemblyManifestInfo info)
{
var existingPermissions = new HashSet<string> (
manifest.Elements ("permission").Select (e => (string?)e.Attribute (AttName)).OfType<string> ());
var existingUsesPermissions = new HashSet<string> (
manifest.Elements ("uses-permission").Select (e => (string?)e.Attribute (AttName)).OfType<string> ());

// <permission> elements
foreach (var perm in info.Permissions) {
if (string.IsNullOrEmpty (perm.Name) || existingPermissions.Contains (perm.Name)) {
continue;
}
var element = new XElement ("permission", new XAttribute (AttName, perm.Name));
PropertyMapper.MapDictionaryProperties (element, perm.Properties, "Label", "label");
PropertyMapper.MapDictionaryProperties (element, perm.Properties, "Description", "description");
PropertyMapper.MapDictionaryProperties (element, perm.Properties, "Icon", "icon");
PropertyMapper.MapDictionaryProperties (element, perm.Properties, "PermissionGroup", "permissionGroup");
PropertyMapper.MapDictionaryEnumProperty (element, perm.Properties, "ProtectionLevel", "protectionLevel", AndroidEnumConverter.ProtectionToString);
manifest.Add (element);
}

// <permission-group> elements
foreach (var pg in info.PermissionGroups) {
if (string.IsNullOrEmpty (pg.Name)) {
continue;
}
var element = new XElement ("permission-group", new XAttribute (AttName, pg.Name));
PropertyMapper.MapDictionaryProperties (element, pg.Properties, "Label", "label");
PropertyMapper.MapDictionaryProperties (element, pg.Properties, "Description", "description");
PropertyMapper.MapDictionaryProperties (element, pg.Properties, "Icon", "icon");
manifest.Add (element);
}

// <permission-tree> elements
foreach (var pt in info.PermissionTrees) {
if (string.IsNullOrEmpty (pt.Name)) {
continue;
}
var element = new XElement ("permission-tree", new XAttribute (AttName, pt.Name));
PropertyMapper.MapDictionaryProperties (element, pt.Properties, "Label", "label");
PropertyMapper.MapDictionaryProperties (element, pt.Properties, "Icon", "icon");
manifest.Add (element);
}

// <uses-permission> elements
foreach (var up in info.UsesPermissions) {
if (string.IsNullOrEmpty (up.Name) || existingUsesPermissions.Contains (up.Name)) {
continue;
}
var element = new XElement ("uses-permission", new XAttribute (AttName, up.Name));
if (up.MaxSdkVersion.HasValue) {
element.SetAttributeValue (AndroidNs + "maxSdkVersion", up.MaxSdkVersion.Value.ToString (CultureInfo.InvariantCulture));
}
manifest.Add (element);
}

// <uses-feature> elements
var existingFeatures = new HashSet<string> (
manifest.Elements ("uses-feature").Select (e => (string?)e.Attribute (AttName)).OfType<string> ());
foreach (var uf in info.UsesFeatures) {
if (uf.Name is not null && !existingFeatures.Contains (uf.Name)) {
var element = new XElement ("uses-feature",
new XAttribute (AttName, uf.Name),
new XAttribute (AndroidNs + "required", uf.Required ? "true" : "false"));
manifest.Add (element);
} else if (uf.GLESVersion != 0) {
var versionStr = $"0x{uf.GLESVersion:X8}";
if (!manifest.Elements ("uses-feature").Any (e => (string?)e.Attribute (AndroidNs + "glEsVersion") == versionStr)) {
var element = new XElement ("uses-feature",
new XAttribute (AndroidNs + "glEsVersion", versionStr),
new XAttribute (AndroidNs + "required", uf.Required ? "true" : "false"));
manifest.Add (element);
}
}
}

// <uses-library> elements inside <application>
foreach (var ul in info.UsesLibraries) {
if (string.IsNullOrEmpty (ul.Name)) {
continue;
}
if (!app.Elements ("uses-library").Any (e => (string?)e.Attribute (AttName) == ul.Name)) {
app.Add (new XElement ("uses-library",
new XAttribute (AttName, ul.Name),
new XAttribute (AndroidNs + "required", ul.Required ? "true" : "false")));
}
}

// Assembly-level <meta-data> inside <application>
foreach (var md in info.MetaData) {
if (string.IsNullOrEmpty (md.Name)) {
continue;
}
if (!app.Elements ("meta-data").Any (e => (string?)e.Attribute (AndroidNs + "name") == md.Name)) {
app.Add (ComponentElementBuilder.CreateMetaDataElement (md));
}
}

// Assembly-level <property> inside <application>
foreach (var prop in info.Properties) {
if (string.IsNullOrEmpty (prop.Name)) {
continue;
}
if (!app.Elements ("property").Any (e => (string?)e.Attribute (AndroidNs + "name") == prop.Name)) {
var element = new XElement ("property",
new XAttribute (AndroidNs + "name", prop.Name));
if (prop.Value is not null) {
element.SetAttributeValue (AndroidNs + "value", prop.Value);
}
if (prop.Resource is not null) {
element.SetAttributeValue (AndroidNs + "resource", prop.Resource);
}
app.Add (element);
}
}

// <uses-configuration> elements
foreach (var uc in info.UsesConfigurations) {
var element = new XElement ("uses-configuration");
if (uc.ReqFiveWayNav) {
element.SetAttributeValue (AndroidNs + "reqFiveWayNav", "true");
}
if (uc.ReqHardKeyboard) {
element.SetAttributeValue (AndroidNs + "reqHardKeyboard", "true");
}
if (uc.ReqKeyboardType is not null) {
element.SetAttributeValue (AndroidNs + "reqKeyboardType", uc.ReqKeyboardType);
}
if (uc.ReqNavigation is not null) {
element.SetAttributeValue (AndroidNs + "reqNavigation", uc.ReqNavigation);
}
if (uc.ReqTouchScreen is not null) {
element.SetAttributeValue (AndroidNs + "reqTouchScreen", uc.ReqTouchScreen);
}
manifest.Add (element);
}
}

internal static void ApplyApplicationProperties (XElement app, Dictionary<string, object?> properties)
{
PropertyMapper.ApplyMappings (app, properties, PropertyMapper.ApplicationPropertyMappings, skipExisting: true);
}

internal static void AddInternetPermission (XElement manifest)
{
if (!manifest.Elements ("uses-permission").Any (p =>
(string?)p.Attribute (AndroidNs + "name") == "android.permission.INTERNET")) {
manifest.Add (new XElement ("uses-permission",
new XAttribute (AndroidNs + "name", "android.permission.INTERNET")));
}
}
}
Loading