Skip to content
Draft
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
1 change: 1 addition & 0 deletions Documentation/docs-mobile/messages/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla
+ [XA4247](xa4247.md): Could not resolve POM file for artifact '{artifact}'.
+ [XA4248](xa4248.md): Could not find NuGet package '{nugetId}' version '{version}' in lock file. Ensure NuGet Restore has run since this `<PackageReference>` was added.
+ [XA4235](xa4249.md): Maven artifact specification '{artifact}' is invalid. The correct format is 'group_id:artifact_id:version'.
+ [XA4250](xa4250.md): Manifest-referenced type '{type}' was not found in any scanned assembly. It may be a framework type.
+ XA4300: Native library '{library}' will not be bundled because it has an unsupported ABI.
+ [XA4301](xa4301.md): Apk already contains the item `xxx`.
+ [XA4302](xa4302.md): Unhandled exception merging \`AndroidManifest.xml\`: {ex}
Expand Down
33 changes: 33 additions & 0 deletions Documentation/docs-mobile/messages/xa4250.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: .NET for Android warning XA4250
description: XA4250 warning code
ms.date: 04/07/2026
f1_keywords:
- "XA4250"
---

# .NET for Android warning XA4250

## Example message

Manifest-referenced type '{0}' was not found in any scanned assembly. It may be a framework type.

```text
warning XA4250: Manifest-referenced type 'com.example.MainActivity' was not found in any scanned assembly. It may be a framework type.
```

## Issue

The build found a type name in `AndroidManifest.xml`, but it could not match that name to any Java peer discovered in the app's managed assemblies.

This can be expected for framework-provided types, but it can also indicate that the manifest entry does not match the name generated for a managed Android component.

## Solution

If the manifest entry refers to an Android framework type, this warning can usually be ignored.

Otherwise:

1. Verify the `android:name` value in the manifest.
2. Ensure the managed type is included in the app build.
3. Check for namespace, `[Register]`, or nested-type naming mismatches between the manifest and the managed type.
12 changes: 11 additions & 1 deletion build-tools/automation/yaml-templates/stage-package-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,20 @@ stages:
testName: Mono.Android.NET_Tests-CoreCLR
project: tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
testResultsFiles: TestResult-Mono.Android.NET_Tests-$(XA.Build.Configuration)CoreCLR.xml
extraBuildArgs: -p:TestsFlavor=CoreCLR -p:UseMonoRuntime=false
extraBuildArgs: -p:_AndroidTypeMapImplementation=llvm-ir
artifactSource: bin/Test$(XA.Build.Configuration)/$(DotNetTargetFramework)-android/Mono.Android.NET_Tests-Signed.aab
artifactFolder: $(DotNetTargetFramework)-CoreCLR

- template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml
parameters:
configuration: $(XA.Build.Configuration)
testName: Mono.Android.NET_Tests-CoreCLRTrimmable
project: tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
testResultsFiles: TestResult-Mono.Android.NET_Tests-$(XA.Build.Configuration)CoreCLRTrimmable.xml
extraBuildArgs: -p:_AndroidTypeMapImplementation=trimmable -p:UseMonoRuntime=false
artifactSource: bin/Test$(XA.Build.Configuration)/$(DotNetTargetFramework)-android/Mono.Android.NET_Tests-Signed.aab
artifactFolder: $(DotNetTargetFramework)-CoreCLRTrimmable

- template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml
parameters:
configuration: $(XA.Build.Configuration)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -29,10 +28,10 @@ public static class AcwMapWriter
public static void Write (TextWriter writer, IEnumerable<JavaPeerInfo> peers)
{
foreach (var peer in peers.OrderBy (p => p.ManagedTypeName, StringComparer.Ordinal)) {
string javaKey = peer.JavaName.Replace ('/', '.');
string javaKey = JniSignatureHelper.JniNameToJavaName (peer.JavaName);
string managedKey = peer.ManagedTypeName;
string partialAsmQualifiedName = $"{managedKey}, {peer.AssemblyName}";
string compatJniName = peer.CompatJniName.Replace ('/', '.');
string compatJniName = JniSignatureHelper.JniNameToJavaName (peer.CompatJniName);

// Line 1: PartialAssemblyQualifiedName;JavaKey
writer.Write (partialAsmQualifiedName);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#nullable enable

using System.Collections.Generic;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Globalization;
Expand All @@ -21,63 +19,73 @@ internal static void AddAssemblyLevelElements (XElement manifest, XElement app,
{
var existingPermissions = new HashSet<string> (
manifest.Elements ("permission").Select (e => (string?)e.Attribute (AttName)).OfType<string> ());
var existingPermissionGroups = new HashSet<string> (
manifest.Elements ("permission-group").Select (e => (string?)e.Attribute (AttName)).OfType<string> ());
var existingPermissionTrees = new HashSet<string> (
manifest.Elements ("permission-tree").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)) {
if (string.IsNullOrEmpty (perm.Name) || !existingPermissions.Add (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, "RoundIcon", "roundIcon");
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)) {
if (string.IsNullOrEmpty (pg.Name) || !existingPermissionGroups.Add (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");
PropertyMapper.MapDictionaryProperties (element, pg.Properties, "RoundIcon", "roundIcon");
manifest.Add (element);
}

// <permission-tree> elements
foreach (var pt in info.PermissionTrees) {
if (string.IsNullOrEmpty (pt.Name)) {
if (string.IsNullOrEmpty (pt.Name) || !existingPermissionTrees.Add (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");
PropertyMapper.MapDictionaryProperties (element, pt.Properties, "RoundIcon", "roundIcon");
manifest.Add (element);
}

// <uses-permission> elements
foreach (var up in info.UsesPermissions) {
if (string.IsNullOrEmpty (up.Name) || existingUsesPermissions.Contains (up.Name)) {
if (string.IsNullOrEmpty (up.Name) || !existingUsesPermissions.Add (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));
}
if (!string.IsNullOrEmpty (up.UsesPermissionFlags)) {
element.SetAttributeValue (AndroidNs + "usesPermissionFlags", up.UsesPermissionFlags);
}
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)) {
if (uf.Name is not null && existingFeatures.Add (uf.Name)) {
var element = new XElement ("uses-feature",
new XAttribute (AttName, uf.Name),
new XAttribute (AndroidNs + "required", uf.Required ? "true" : "false"));
Expand Down Expand Up @@ -153,11 +161,59 @@ internal static void AddAssemblyLevelElements (XElement manifest, XElement app,
}
manifest.Add (element);
}

// <supports-gl-texture> elements
var existingGLTextures = new HashSet<string> (
manifest.Elements ("supports-gl-texture").Select (e => (string?)e.Attribute (AttName)).OfType<string> ());
foreach (var gl in info.SupportsGLTextures) {
if (existingGLTextures.Add (gl.Name)) {
manifest.Add (new XElement ("supports-gl-texture", new XAttribute (AttName, gl.Name)));
}
}
}

internal static void ApplyApplicationProperties (XElement app, Dictionary<string, object?> properties)
internal static void ApplyApplicationProperties (
XElement app,
Dictionary<string, object?> properties,
IReadOnlyList<JavaPeerInfo> allPeers,
Action<string>? warn = null)
{
PropertyMapper.ApplyMappings (app, properties, PropertyMapper.ApplicationPropertyMappings, skipExisting: true);

// BackupAgent and ManageSpaceActivity are Type properties — resolve managed type names to JNI names
ApplyTypeProperty (app, properties, allPeers, "BackupAgent", "backupAgent", warn);
ApplyTypeProperty (app, properties, allPeers, "ManageSpaceActivity", "manageSpaceActivity", warn);
}

static void ApplyTypeProperty (
XElement app,
Dictionary<string, object?> properties,
IReadOnlyList<JavaPeerInfo> allPeers,
string propertyName,
string xmlAttrName,
Action<string>? warn)
{
if (app.Attribute (AndroidNs + xmlAttrName) is not null) {
return;
}
if (!properties.TryGetValue (propertyName, out var value) || value is not string managedName || managedName.Length == 0) {
return;
}

// Strip assembly qualification if present (e.g., "MyApp.MyAgent, MyAssembly")
var commaIndex = managedName.IndexOf (',');
if (commaIndex > 0) {
managedName = managedName.Substring (0, commaIndex).Trim ();
}

foreach (var peer in allPeers) {
if (peer.ManagedTypeName == managedName) {
app.SetAttributeValue (AndroidNs + xmlAttrName, peer.JavaName.Replace ('/', '.'));
return;
}
}

warn?.Invoke ($"Could not resolve {propertyName} type '{managedName}' to a Java peer for android:{xmlAttrName}.");
}

internal static void AddInternetPermission (XElement manifest)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#nullable enable

using System;
using System.Globalization;
using System.Linq;
Expand Down Expand Up @@ -157,7 +155,7 @@ internal static XElement CreateMetaDataElement (MetaDataInfo meta)

internal static void UpdateApplicationElement (XElement app, JavaPeerInfo peer)
{
string jniName = peer.JavaName.Replace ('/', '.');
string jniName = JniSignatureHelper.JniNameToJavaName (peer.JavaName);
app.SetAttributeValue (AttName, jniName);

var component = peer.ComponentAttribute;
Expand All @@ -167,9 +165,9 @@ internal static void UpdateApplicationElement (XElement app, JavaPeerInfo peer)
PropertyMapper.ApplyMappings (app, component.Properties, PropertyMapper.ApplicationElementMappings);
}

internal static void AddInstrumentation (XElement manifest, JavaPeerInfo peer)
internal static void AddInstrumentation (XElement manifest, JavaPeerInfo peer, string packageName)
{
string jniName = peer.JavaName.Replace ('/', '.');
string jniName = JniSignatureHelper.JniNameToJavaName (peer.JavaName);
var element = new XElement ("instrumentation",
new XAttribute (AttName, jniName));

Expand All @@ -178,6 +176,9 @@ internal static void AddInstrumentation (XElement manifest, JavaPeerInfo peer)
return;
}
PropertyMapper.ApplyMappings (element, component.Properties, PropertyMapper.InstrumentationMappings);
if (element.Attribute (AndroidNs + "targetPackage") is null && !string.IsNullOrEmpty (packageName)) {
element.SetAttributeValue (AndroidNs + "targetPackage", packageName);
}

manifest.Add (element);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,28 @@ static void WriteClassDeclaration (JavaPeerInfo type, TextWriter writer)

static void WriteStaticInitializer (JavaPeerInfo type, TextWriter writer)
{
string className = JniSignatureHelper.GetJavaSimpleName (type.JavaName);

// Application and Instrumentation types cannot call registerNatives in their
// static initializer — the native library isn't loaded yet at that point.
// Their registration is deferred to ApplicationRegistration.registerApplications().
// static initializer — the runtime isn't ready yet at that point. Emit a
// lazy one-time helper instead so the first managed callback can register
// the class just before invoking its native method.
if (type.CannotRegisterInStaticConstructor) {
writer.Write ($$"""
private static boolean __md_natives_registered;
private static synchronized void __md_registerNatives ()
{
if (!__md_natives_registered) {
mono.android.Runtime.registerNatives ({{className}}.class);
__md_natives_registered = true;
}
}


""");
return;
}

string className = JniSignatureHelper.GetJavaSimpleName (type.JavaName);
writer.Write ($$"""
static {
mono.android.Runtime.registerNatives ({{className}}.class);
Expand All @@ -153,7 +167,17 @@ static void WriteConstructors (JavaPeerInfo type, TextWriter writer)
public {{simpleClassName}} ({{parameters}})
{
super ({{superArgs}});

""");

if (!type.CannotRegisterInStaticConstructor) {
writer.Write ($$"""
if (getClass () == {{simpleClassName}}.class) nctor_{{ctor.ConstructorIndex}} ({{args}});

""");
}

writer.Write ($$"""
}


Expand Down Expand Up @@ -196,6 +220,10 @@ static void WriteFields (JavaPeerInfo type, TextWriter writer)

static void WriteMethods (JavaPeerInfo type, TextWriter writer)
{
string registerNativesLine = type.CannotRegisterInStaticConstructor
? "\t\t__md_registerNatives ();\n"
: "";

foreach (var method in type.MarshalMethods) {
if (method.IsConstructor) {
continue;
Expand All @@ -221,7 +249,7 @@ static void WriteMethods (JavaPeerInfo type, TextWriter writer)
@Override
public {{javaReturnType}} {{method.JniName}} ({{parameters}}){{throwsClause}}
{
{{returnPrefix}}{{method.NativeCallbackName}} ({{args}});
{{registerNativesLine}} {{returnPrefix}}{{method.NativeCallbackName}} ({{args}});
}
public native {{javaReturnType}} {{method.NativeCallbackName}} ({{parameters}});

Expand All @@ -232,7 +260,7 @@ static void WriteMethods (JavaPeerInfo type, TextWriter writer)

{{access}} {{javaReturnType}} {{method.JniName}} ({{parameters}}){{throwsClause}}
{
{{returnPrefix}}{{method.NativeCallbackName}} ({{args}});
{{registerNativesLine}} {{returnPrefix}}{{method.NativeCallbackName}} ({{args}});
}
{{access}} native {{javaReturnType}} {{method.NativeCallbackName}} ({{parameters}});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#nullable enable

using System.Xml.Linq;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;
Expand Down
Loading