Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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