Skip to content

Commit

Permalink
[Microsoft.Android.Sdk.ILLink] preserve types with `IJniNameProviderA…
Browse files Browse the repository at this point in the history
…ttribute` (#9099)

Fixes: #8940
Context: TobiasBuchholz/Plugin.Firebase#144

Using the NuGet package:

    <PackageReference Include="Plugin.Firebase.CloudMessaging" Version="3.0.0" />

Includes a service:

    namespace Plugin.Firebase.CloudMessaging.Platforms.Android;

    [Service(Exported = true)]
    [IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
    public class MyFirebaseMessagingService : FirebaseMessagingService

Unfortunately, using `TrimMode=full` completely trims away the above
service, which is required for push notifications to work.

I could reproduce this problem in a test using the above NuGet package.

To fix this, we can modify `MarkJavaObjects` to preserve types with
attributes that implement `Java.Interop.IJniNameProviderAttribute`,
and the new test now passes.

With one exception, `Android.Runtime.RegisterAttribute`, should not be
preserved as that would be any Java type bound for C#.
  • Loading branch information
jonathanpeppers authored Jul 15, 2024
1 parent 83626c9 commit 611c269
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 3 deletions.
41 changes: 38 additions & 3 deletions src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
using Mono.Cecil;
using Mono.Linker;
using Mono.Linker.Steps;
using Mono.Tuner;
using Java.Interop.Tools.Cecil;
using Xamarin.Android.Tasks;
using Profile = Microsoft.Android.Sdk.ILLink.Profile;

namespace MonoDroid.Tuner {

Expand All @@ -27,7 +27,14 @@ public override void Initialize (LinkContext context, MarkContext markContext)

bool IsActiveFor (AssemblyDefinition assembly)
{
return assembly.MainModule.HasTypeReference ("System.Net.Http.HttpMessageHandler") || assembly.MainModule.HasTypeReference ("Android.Util.IAttributeSet");
if (Profile.IsSdkAssembly (assembly))
return false;
if (Profile.IsProductAssembly (assembly))
return false;

return assembly.MainModule.HasTypeReference ("System.Net.Http.HttpMessageHandler") ||
assembly.MainModule.HasTypeReference ("Java.Lang.Object") ||
assembly.MainModule.HasTypeReference ("Android.Util.IAttributeSet");
}

public void ProcessAssembly (AssemblyDefinition assembly, string androidHttpClientHandlerType, Dictionary<string, HashSet<string>> customViewMap)
Expand All @@ -47,14 +54,42 @@ public void ProcessAssembly (AssemblyDefinition assembly, string androidHttpClie
}
}

// Custom views in Android .xml files
// Continue if not an IJavaObject
if (!type.ImplementsIJavaObject (cache))
continue;

// Custom views in Android .xml files
if (customViewMap.ContainsKey (type.FullName)) {
Annotations.Mark (type);
PreserveJavaObjectImplementation (type);
continue;
}

// Types with Java.Interop.IJniNameProviderAttribute attributes
if (ShouldPreserveBasedOnAttributes (type)) {
Annotations.Mark (type);
PreserveJavaObjectImplementation (type);
continue;
}
}
}

bool ShouldPreserveBasedOnAttributes (TypeDefinition type)
{
if (!type.HasCustomAttributes)
return false;

foreach (var attr in type.CustomAttributes) {
// Ignore Android.Runtime.RegisterAttribute
if (attr.AttributeType.FullName == "Android.Runtime.RegisterAttribute") {
continue;
}
if (attr.AttributeType.Implements ("Java.Interop.IJniNameProviderAttribute", cache)) {
return true;
}
}

return false;
}

public void ProcessType (TypeDefinition type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,40 @@ public void PreserveIX509TrustManagerSubclasses ([Values(true, false)] bool hasS
}
}

[Test]
public void PreserveServices ()
{
var proj = new XamarinAndroidApplicationProject {
IsRelease = true,
TrimModeRelease = TrimMode.Full,
PackageReferences = {
new Package { Id = "Plugin.Firebase.CloudMessaging", Version = "3.0.0" },
}
};
proj.MainActivity = proj.DefaultMainActivity
.Replace ("//${FIELDS}",
"""
protected override void OnNewIntent (Android.Content.Intent? intent)
{
base.OnNewIntent (intent);
Plugin.Firebase.CloudMessaging.FirebaseCloudMessagingImplementation.OnNewIntent (intent);
}
""")
.Replace ("//${AFTER_ONCREATE}", "Plugin.Firebase.Core.Platforms.Android.CrossFirebase.Initialize (this);");

using var b = CreateApkBuilder ();
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");

var assemblyPath = BuildTest.GetLinkedPath (b, isRelease: true, "Plugin.Firebase.CloudMessaging.dll");
FileAssert.Exists (assemblyPath);
using var assembly = AssemblyDefinition.ReadAssembly (assemblyPath);
var types = new [] { "Plugin.Firebase.CloudMessaging.Platforms.Android.MyFirebaseMessagingService" };
foreach (var typeName in types) {
var td = assembly.MainModule.GetType (typeName);
Assert.IsNotNull (td, $"{typeName} should not have been linked out!");
}
}

[Test]
public void DoNotErrorOnPerArchJavaTypeDuplicates ([Values(true, false)] bool enableMarshalMethods)
{
Expand Down

0 comments on commit 611c269

Please sign in to comment.