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
Expand Up @@ -45,14 +45,14 @@ public sealed class JcwJavaSourceGenerator
/// Generates .java source content for all ACW types and returns them as in-memory
/// (relativePath, content) pairs. No filesystem IO is performed.
/// </summary>
public IReadOnlyList<GeneratedJavaSource> GenerateContent (IReadOnlyList<JavaPeerInfo> types)
public IReadOnlyList<GeneratedJavaSource> GenerateContent (IReadOnlyList<JavaPeerInfo> types, string? applicationJavaClass = null)
{
if (types is null) throw new ArgumentNullException (nameof (types));
var results = new List<GeneratedJavaSource> ();
foreach (var type in types) {
if (type.DoNotGenerateAcw || type.IsInterface) continue;
using var writer = new StringWriter ();
Generate (type, writer);
Generate (type, writer, applicationJavaClass);
results.Add (new GeneratedJavaSource (GetRelativePath (type), writer.ToString ()));
}
return results;
Expand All @@ -61,11 +61,11 @@ public IReadOnlyList<GeneratedJavaSource> GenerateContent (IReadOnlyList<JavaPee
/// <summary>
/// Generates a single .java source file for the given type.
/// </summary>
public void Generate (JavaPeerInfo type, TextWriter writer)
public void Generate (JavaPeerInfo type, TextWriter writer, string? applicationJavaClass = null)
{
writer.NewLine = "\n";
WritePackageDeclaration (type, writer);
WriteClassDeclaration (type, writer);
WriteClassDeclaration (type, writer, applicationJavaClass);
WriteStaticInitializer (type, writer);
WriteConstructors (type, writer);
WriteFields (type, writer);
Expand Down Expand Up @@ -97,7 +97,7 @@ static void WritePackageDeclaration (JavaPeerInfo type, TextWriter writer)
}
}

static void WriteClassDeclaration (JavaPeerInfo type, TextWriter writer)
static void WriteClassDeclaration (JavaPeerInfo type, TextWriter writer, string? applicationJavaClass)
{
string abstractModifier = type.IsAbstract && !type.IsInterface ? "abstract " : "";
string className = JniSignatureHelper.GetJavaSimpleName (type.JavaName);
Expand All @@ -106,7 +106,14 @@ static void WriteClassDeclaration (JavaPeerInfo type, TextWriter writer)

// extends clause
if (type.BaseJavaName != null) {
writer.WriteLine ($"\textends {JniSignatureHelper.JniNameToJavaName (type.BaseJavaName)}");
string baseName = JniSignatureHelper.JniNameToJavaName (type.BaseJavaName);
// A user Application subclass normally extends android.app.Application, but when
// $(AndroidApplicationJavaClass) is set (e.g. android.support.multidex.MultiDexApplication
// when $(AndroidEnableMultiDex) is true) it must extend that class instead.
if (!applicationJavaClass.IsNullOrEmpty () && baseName == "android.app.Application") {
baseName = applicationJavaClass;
}
writer.WriteLine ($"\textends {baseName}");
}

// implements clause — always includes IGCUserPeer, plus any implemented interfaces
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public TrimmableTypeMapResult Execute (
}
MarkFrameworkAssemblyPeers (allPeers, frameworkAssemblyNames);

RootManifestReferencedTypes (allPeers, PrepareManifestForRooting (manifestTemplate, manifestConfig));
RootManifestReferencedTypes (allPeers, PrepareManifestForRooting (manifestTemplate, manifestConfig), manifestConfig?.ApplicationJavaClass);
PropagateDeferredRegistrationToBaseClasses (allPeers);
PropagateCannotRegisterToDescendants (allPeers);

Expand All @@ -60,7 +60,7 @@ public TrimmableTypeMapResult Execute (
: [];
var jcwPeers = allPeers.Where (ShouldGenerateJcw).ToList ();
logger.LogGeneratingJcwFilesInfo (jcwPeers.Count, allPeers.Count);
var generatedJavaSources = GenerateJcwJavaSources (jcwPeers);
var generatedJavaSources = GenerateJcwJavaSources (jcwPeers, manifestConfig?.ApplicationJavaClass);

var appRegTypes = CollectApplicationRegistrationTypes (allPeers);
if (appRegTypes.Count > 0) {
Expand Down Expand Up @@ -289,15 +289,15 @@ static void MarkFrameworkAssemblyPeers (List<JavaPeerInfo> allPeers, HashSet<str
return result;
}

List<GeneratedJavaSource> GenerateJcwJavaSources (List<JavaPeerInfo> allPeers)
List<GeneratedJavaSource> GenerateJcwJavaSources (List<JavaPeerInfo> allPeers, string? applicationJavaClass)
{
var jcwGenerator = new JcwJavaSourceGenerator ();
var sources = jcwGenerator.GenerateContent (allPeers);
var sources = jcwGenerator.GenerateContent (allPeers, applicationJavaClass);
logger.LogGeneratedJcwFilesInfo (sources.Count);
return sources.ToList ();
}

internal void RootManifestReferencedTypes (List<JavaPeerInfo> allPeers, XDocument? doc)
internal void RootManifestReferencedTypes (List<JavaPeerInfo> allPeers, XDocument? doc, string? applicationJavaClass = null)
{
if (doc?.Root is not { } root) {
return;
Expand Down Expand Up @@ -357,6 +357,13 @@ internal void RootManifestReferencedTypes (List<JavaPeerInfo> allPeers, XDocumen
}
}
} else {
// $(AndroidApplicationJavaClass) (e.g. android.support.multidex.MultiDexApplication
// when $(AndroidEnableMultiDex) is true) is a Java framework type with no managed
// peer, so it is expected to be absent from the scanned assemblies — don't warn.
if (!applicationJavaClass.IsNullOrEmpty () &&
string.Equals (name, ManifestNameResolver.Resolve (applicationJavaClass, packageName), StringComparison.Ordinal)) {
continue;
}
logger.LogManifestReferencedTypeNotFoundWarning (name);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,46 @@ public void Generate_ClickableView_UsesDotsForNestedInterfaceName ()
Assert.DoesNotContain ("View$OnClickListener", java);
}

[Fact]
public void Generate_ApplicationSubclass_WithApplicationJavaClass_ExtendsThatClass ()
{
// When $(AndroidApplicationJavaClass) is set (e.g. android.support.multidex.MultiDexApplication
// under $(AndroidEnableMultiDex)), an android.app.Application subclass must extend it.
var type = new JavaPeerInfo {
JavaName = "com/foxsports/test/CustomApp",
CompatJniName = "com/foxsports/test/CustomApp",
ManagedTypeName = "UnnamedProject.CustomApp",
ManagedTypeNamespace = "UnnamedProject",
ManagedTypeShortName = "CustomApp",
AssemblyName = "App",
BaseJavaName = "android/app/Application",
CannotRegisterInStaticConstructor = true,
};
var generator = new JcwJavaSourceGenerator ();
using var writer = new StringWriter ();
generator.Generate (type, writer, "android.support.multidex.MultiDexApplication");
var java = writer.ToString ();
Assert.Contains ("extends android.support.multidex.MultiDexApplication", java);
Assert.DoesNotContain ("extends android.app.Application", java);
}

[Fact]
public void Generate_ApplicationSubclass_WithoutApplicationJavaClass_ExtendsApplication ()
{
var type = new JavaPeerInfo {
JavaName = "com/foxsports/test/CustomApp",
CompatJniName = "com/foxsports/test/CustomApp",
ManagedTypeName = "UnnamedProject.CustomApp",
ManagedTypeNamespace = "UnnamedProject",
ManagedTypeShortName = "CustomApp",
AssemblyName = "App",
BaseJavaName = "android/app/Application",
CannotRegisterInStaticConstructor = true,
};
var java = GenerateToString (type);
Assert.Contains ("extends android.app.Application", java);
}

}

public class StaticInitializer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,34 @@ public void RootManifestReferencedTypes_WarnsForUnresolvedTypes ()
Assert.Contains (warnings, w => w.Contains ("com.example.NonExistentService"));
}

[Fact]
public void RootManifestReferencedTypes_DoesNotWarnForApplicationJavaClass ()
{
// android.support.multidex.MultiDexApplication (injected via $(AndroidApplicationJavaClass)
// when $(AndroidEnableMultiDex) is true) is a Java framework type with no managed peer,
// so it must not produce an XA4250 "not found in any scanned assembly" warning.
var peers = new List<JavaPeerInfo> {
new JavaPeerInfo {
JavaName = "com/example/MyActivity", CompatJniName = "com.example.MyActivity",
ManagedTypeName = "MyApp.MyActivity", ManagedTypeNamespace = "MyApp", ManagedTypeShortName = "MyActivity",
AssemblyName = "MyApp",
},
};

var doc = System.Xml.Linq.XDocument.Parse ("""
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example">
<application android:name="android.support.multidex.MultiDexApplication" />
</manifest>
""");

var warnings = new List<string> ();
var generator = CreateGenerator (warnings);
generator.RootManifestReferencedTypes (peers, doc, "android.support.multidex.MultiDexApplication");

Assert.DoesNotContain (warnings, w => w.Contains ("MultiDexApplication"));
}

[Fact]
public void RootManifestReferencedTypes_SkipsAlreadyUnconditional ()
{
Expand Down
Loading