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
Original file line number Diff line number Diff line change
Expand Up @@ -235,14 +235,21 @@ static bool IsUnconditionalEntry (JavaPeerInfo peer)
return true;
}

// User-defined ACW types (not MCW bindings, not interfaces) are unconditional
// because Android can instantiate them from Java at any time.
if (!peer.IsFrameworkAssembly && !peer.DoNotGenerateAcw && !peer.IsInterface) {
// Types marked unconditional by the scanner (component attributes: Activity, Service, etc.)
if (peer.IsUnconditional) {
return true;
}

// Types marked unconditional by the scanner (component attributes: Activity, Service, etc.)
if (peer.IsUnconditional) {
// Binding-generated listener implementors are instantiated by the generated managed event
// accessors before being passed to Java. Existing binding assemblies can be recognized by
// their generated metadata shape, so their entries can follow managed reachability.
if (peer.IsBindingEventListenerImplementor) {
return false;
}

// User-defined ACW types (not MCW bindings, not interfaces) are unconditional
// because Android can instantiate them from Java at any time.
if (!peer.IsFrameworkAssembly && !peer.DoNotGenerateAcw && !peer.IsInterface) {
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ public sealed record JavaPeerInfo
/// </summary>
public bool DoNotGenerateAcw { get; init; }

/// <summary>
/// True when the type matches the existing binding-generator listener implementor shape:
/// a generated <c>mono/</c> ACW implementing a Java interface with the static
/// <c>__IsEmpty(TImplementor)</c> helper used by event removal.
/// </summary>
public bool IsBindingEventListenerImplementor { get; init; }

/// <summary>
/// True when the type was discovered via <c>[JniTypeSignatureAttribute]</c>
/// rather than <c>[RegisterAttribute]</c>. Used to resolve cross-assembly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,8 @@ void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string A
IsInterface = isInterface,
IsAbstract = isAbstract,
DoNotGenerateAcw = doNotGenerateAcw,
IsBindingEventListenerImplementor = IsBindingEventListenerImplementor (
typeDef, index, jniName, fullName, isInterface, doNotGenerateAcw, implementedInterfaces),
IsFromJniTypeSignature = registerInfo?.IsFromJniTypeSignature ?? false,
IsUnconditional = isUnconditional,
CannotRegisterInStaticConstructor = cannotRegisterInStaticConstructor,
Expand All @@ -405,6 +407,41 @@ void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string A
}
}

static bool IsBindingEventListenerImplementor (
TypeDefinition typeDef,
AssemblyIndex index,
string jniName,
string managedTypeName,
bool isInterface,
bool doNotGenerateAcw,
IReadOnlyList<string> implementedInterfaces)
{
if (isInterface ||
doNotGenerateAcw ||
!jniName.StartsWith ("mono/", StringComparison.Ordinal) ||
implementedInterfaces.Count == 0 ||
(typeDef.Attributes & TypeAttributes.Sealed) == 0) {
return false;
}

foreach (var methodHandle in typeDef.GetMethods ()) {
var methodDef = index.Reader.GetMethodDefinition (methodHandle);
if ((methodDef.Attributes & MethodAttributes.Static) == 0 ||
index.Reader.GetString (methodDef.Name) != "__IsEmpty") {
continue;
}

var signature = methodDef.DecodeSignature (TypeRefSignatureTypeProvider.Instance, genericContext: index);
if (signature.ReturnType.ManagedTypeName == "System.Boolean" &&
signature.ParameterTypes.Length == 1 &&
signature.ParameterTypes [0].ManagedTypeName == managedTypeName) {
return true;
}
}

return false;
}

bool IsResolvableJavaPeerType (
TypeDefinitionHandle typeDefHandle,
AssemblyIndex index,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,31 @@ public void Build_UserAcwType_IsUnconditional ()
Assert.Null (mainEntry.TargetTypeReference);
}

[Fact]
public void Build_BindingEventListenerImplementor_IsTrimmable ()
{
var peer = MakeAcwPeer ("mono/example/ListenerImplementor", "Example.ListenerImplementor", "Binding") with {
IsBindingEventListenerImplementor = true,
};
var model = BuildModel ([peer]);

Assert.False (model.Entries [0].IsUnconditional);
Assert.Equal ("Example.ListenerImplementor, Binding", model.Entries [0].TargetTypeReference);
}

[Fact]
public void Build_BindingEventListenerImplementor_MarkedUnconditional_IsUnconditional ()
{
var peer = MakeAcwPeer ("mono/example/ListenerImplementor", "Example.ListenerImplementor", "Binding") with {
IsBindingEventListenerImplementor = true,
IsUnconditional = true,
};
var model = BuildModel ([peer]);

Assert.True (model.Entries [0].IsUnconditional);
Assert.Null (model.Entries [0].TargetTypeReference);
}

[Fact]
public void Build_FrameworkAcwType_IsTrimmable ()
{
Expand Down Expand Up @@ -754,21 +779,20 @@ public void Fixture_AcwType_HasProxy (string javaName, string expectedProxyName)
public class FixtureImplementorsAndDispatchers
{
[Theory]
[InlineData ("mono/android/view/View_IOnClickListenerImplementor", "Implementor")]
[InlineData ("mono/android/view/View_ClickEventDispatcher", "EventDispatcher")]
public void Fixture_HelperType_IsUnconditional (string javaName, string kind)
[InlineData ("mono/android/view/View_IOnClickListenerImplementor", true)]
[InlineData ("mono/android/view/View_ClickEventDispatcher", false)]
public void Fixture_HelperType_UsesGeneratedShapeDetection (string javaName, bool isBindingEventListenerImplementor)
{
var peer = FindFixtureByJavaName (javaName);
Assert.False (peer.DoNotGenerateAcw);
Assert.False (peer.IsInterface);
Assert.Equal (isBindingEventListenerImplementor, peer.IsBindingEventListenerImplementor);

var model = BuildModel (new [] { peer }, "TypeMap");

var entry = model.Entries.FirstOrDefault ();
Assert.NotNull (entry);
// Implementor/EventDispatcher types are treated as unconditional ACW types.
// Future optimization (see #10911) may make them trimmable.
Assert.True (entry.IsUnconditional, $"{kind} should be unconditional");
Assert.Equal (!isBindingEventListenerImplementor, entry.IsUnconditional);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ public void Scan_InvokerAndInterface_ShareJavaName ()
Assert.Contains (clickListenerPeers, p => p.DoNotGenerateAcw);
}

[Fact]
public void Scan_DetectsBindingEventListenerImplementorShape ()
{
Assert.True (FindFixtureByJavaName ("mono/android/view/View_IOnClickListenerImplementor").IsBindingEventListenerImplementor);
Assert.False (FindFixtureByJavaName ("mono/android/view/FakeListenerPeer").IsBindingEventListenerImplementor);
Assert.False (FindFixtureByJavaName ("mono/android/view/View_ClickEventDispatcher").IsBindingEventListenerImplementor);
Assert.False (FindFixtureByJavaName ("my/app/MainActivity").IsBindingEventListenerImplementor);
}

[Fact]
public void Scan_AllTypes_HaveAssemblyName ()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ namespace Android.Views
public class View : Java.Lang.Object
{
protected View (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { }

[Register ("mono/android/view/View_IOnClickListenerImplementor")]
internal sealed class IOnClickListenerImplementor : Java.Lang.Object, IOnClickListener
{
#pragma warning disable 0649
public EventHandler? Handler;
#pragma warning restore 0649

public IOnClickListenerImplementor (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { }
public void OnClick (View v) => Handler?.Invoke (this, EventArgs.Empty);
internal static bool __IsEmpty (IOnClickListenerImplementor value) => value.Handler is null;
}
}

[Register ("android/view/View$OnClickListener", "", "Android.Views.IOnClickListenerInvoker")]
Expand Down Expand Up @@ -186,10 +198,11 @@ public interface INamedClickListener : IOnClickListener
string? Label { get; }
}

[Register ("mono/android/view/View_IOnClickListenerImplementor")]
public class View_IOnClickListenerImplementor : Java.Lang.Object
[Register ("mono/android/view/FakeListenerPeer")]
internal sealed class FakeListenerPeer : Java.Lang.Object, IOnClickListener
{
public View_IOnClickListenerImplementor (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { }
public FakeListenerPeer (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { }
public void OnClick (View v) { }
}

[Register ("mono/android/view/View_ClickEventDispatcher")]
Expand Down
Loading