Skip to content
Closed
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 @@ -673,7 +673,8 @@ bool ExtendsJavaPeer (TypeDefinition typeDef, AssemblyIndex index)

string compatName = ns.Length == 0
? typeName
: $"{ns.ToLowerInvariant ().Replace ('.', '/')}/{typeName}";
// Compat format preserves namespace dots: UserApp.Models -> userapp.models/TypeName
: $"{ns.ToLowerInvariant ()}/{typeName}";

return (jniName, compatName);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Xunit;

namespace Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests;

public partial class ScannerComparisonTests
{
static readonly Lazy<List<JavaPeerInfo>> MonoAndroidPeers = new (() => ScanPeers (AllAssemblyPaths));
static readonly Lazy<List<JavaPeerInfo>> UserFixturePeers = new (() => {
var paths = AllUserTypesAssemblyPaths
?? throw new InvalidOperationException ("UserTypesFixture.dll not found.");
return ScanPeers (paths);
});

[Fact]
public void IsUnconditional_ComponentTypes ()
{
Assert.True (FindUserPeerByManagedName ("UserApp.MainActivity").IsUnconditional);
Assert.True (FindUserPeerByManagedName ("UserApp.Services.MyBackgroundService").IsUnconditional);
Assert.True (FindUserPeerByManagedName ("UserApp.Receivers.BootReceiver").IsUnconditional);
Assert.True (FindUserPeerByManagedName ("UserApp.Providers.SettingsProvider").IsUnconditional);

Assert.False (FindUserPeerByManagedName ("UserApp.PlainActivity").IsUnconditional);
Assert.False (FindUserPeerByManagedName ("UserApp.Listeners.MyClickListener").IsUnconditional);

Assert.True (FindUserPeerByManagedName ("UserApp.MyBackupAgent").IsUnconditional);
}

[Fact]
public void IsUnconditional_MonoAndroid ()
{
var peers = ScanMonoAndroidPeers ();

Assert.NotEmpty (peers);
Assert.Equal (0, peers.Count (p => p.IsUnconditional));
Assert.All (peers.Where (p => p.DoNotGenerateAcw), peer =>
Assert.False (peer.IsUnconditional, $"{peer.ManagedTypeName} should not be unconditional."));
}

[Fact]
public void InvokerTypeName_InterfacesAndAbstractTypes_MonoAndroid ()
{
var peers = ScanMonoAndroidPeers ();
var managedNames = peers.Select (p => p.ManagedTypeName).ToHashSet (StringComparer.Ordinal);
var interfaces = peers.Where (p => p.IsInterface).ToList ();

Assert.NotEmpty (interfaces);
Assert.All (interfaces, peer => {
var invokerTypeName = peer.InvokerTypeName;
Assert.False (string.IsNullOrEmpty (invokerTypeName), $"{peer.ManagedTypeName} should have an invoker.");
Assert.Contains (invokerTypeName, managedNames);
});

var clickListener = interfaces.Single (p => p.JavaName == "android/view/View$OnClickListener");
Assert.Equal ("Android.Views.View+IOnClickListenerInvoker", clickListener.InvokerTypeName);
Assert.Contains ("Android.Views.View+IOnClickListenerInvoker", managedNames);

var absListView = FindMonoAndroidPeerByManagedName ("Android.Widget.AbsListView");
Assert.Equal ("Android.Widget.AbsListViewInvoker", absListView.InvokerTypeName);
Assert.Contains ("Android.Widget.AbsListViewInvoker", managedNames);
}

[Fact]
public void InvokerTypeName_UserTypes ()
{
Assert.Null (FindUserPeerByManagedName ("UserApp.MainActivity").InvokerTypeName);
Assert.Null (FindUserPeerByManagedName ("UserApp.Listeners.MyClickListener").InvokerTypeName);

Assert.Equal ("UserApp.Interfaces.IWidgetListenerInvoker",
FindUserPeerByManagedName ("UserApp.Interfaces.IWidgetListener").InvokerTypeName);
Assert.Equal ("UserApp.AbstractWidgets.AbstractWidgetInvoker",
FindUserPeerByManagedName ("UserApp.AbstractWidgets.AbstractWidget").InvokerTypeName);
}

[Fact]
public void CompatJniName_UserTypes ()
{
var userModel = FindUserPeerByManagedName ("UserApp.Models.UserModel");
Assert.StartsWith ("crc64", userModel.JavaName);
Assert.Equal ("userapp.models/UserModel", userModel.CompatJniName);

var dataManager = FindUserPeerByManagedName ("UserApp.Models.DataManager");
Assert.StartsWith ("crc64", dataManager.JavaName);
Assert.Equal ("userapp.models/DataManager", dataManager.CompatJniName);
}

[Fact]
public void ManagedMethodName_MarshalMethods ()
{
var peers = ScanMonoAndroidPeers ();
var marshalMethods = peers.SelectMany (p => p.MarshalMethods).ToList ();

Assert.NotEmpty (marshalMethods);
Assert.All (marshalMethods, method => Assert.False (string.IsNullOrWhiteSpace (method.ManagedMethodName)));

var activity = FindMonoAndroidPeerByJavaName ("android/app/Activity");
Assert.Contains (activity.MarshalMethods, method =>
method.ManagedMethodName == "OnCreate" && method.JniName == "onCreate");
}

static List<JavaPeerInfo> ScanMonoAndroidPeers () => MonoAndroidPeers.Value;

static List<JavaPeerInfo> ScanUserFixturePeers () => UserFixturePeers.Value;

static List<JavaPeerInfo> ScanPeers (string[] assemblyPaths)
{
var primaryAssemblyName = Path.GetFileNameWithoutExtension (assemblyPaths [0]);
using var scanner = new JavaPeerScanner ();
return scanner.Scan (assemblyPaths)
.Where (p => p.AssemblyName == primaryAssemblyName)
.ToList ();
}

static JavaPeerInfo FindMonoAndroidPeerByJavaName (string javaName)
=> FindPeerByJavaName (ScanMonoAndroidPeers (), javaName);

static JavaPeerInfo FindMonoAndroidPeerByManagedName (string managedTypeName)
=> FindPeerByManagedName (ScanMonoAndroidPeers (), managedTypeName);

static JavaPeerInfo FindUserPeerByManagedName (string managedTypeName)
=> FindPeerByManagedName (ScanUserFixturePeers (), managedTypeName);

static JavaPeerInfo FindPeerByJavaName (IEnumerable<JavaPeerInfo> peers, string javaName)
{
var peer = peers.FirstOrDefault (p => p.JavaName == javaName);
return peer ?? throw new InvalidOperationException ($"Peer with Java name '{javaName}' not found.");
}

static JavaPeerInfo FindPeerByManagedName (IEnumerable<JavaPeerInfo> peers, string managedTypeName)
{
var peer = peers.FirstOrDefault (p => p.ManagedTypeName == managedTypeName);
return peer ?? throw new InvalidOperationException ($"Peer with managed name '{managedTypeName}' not found.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,26 @@ public override void OnReceive (Context? context, Intent? intent)
}
}

namespace UserApp.Providers
{
// Authorities use Android manifest naming conventions; Name is the generated JNI type name.
[ContentProvider (new [] { "com.example.userapp.settingsprovider" }, Name = "com.example.userapp.SettingsProvider")]
public class SettingsProvider : ContentProvider
{
public override bool OnCreate () => true;

public override int Delete (Android.Net.Uri? uri, string? selection, string[]? selectionArgs) => 0;

public override string? GetType (Android.Net.Uri? uri) => null;

public override Android.Net.Uri? Insert (Android.Net.Uri? uri, ContentValues? values) => null;

public override Android.Database.ICursor? Query (Android.Net.Uri? uri, string[]? projection, string? selection, string[]? selectionArgs, string? sortOrder) => null;

public override int Update (Android.Net.Uri? uri, ContentValues? values, string? selection, string[]? selectionArgs) => 0;
}
}

namespace UserApp
{
public class MyBackupAgent : Android.App.Backup.BackupAgent
Expand Down Expand Up @@ -92,6 +112,57 @@ public MyApp (IntPtr handle, JniHandleOwnership transfer)
}
}

namespace UserApp.Interfaces
{
[Register ("com/example/userapp/IWidgetListener", "", "UserApp.Interfaces.IWidgetListenerInvoker")]
public interface IWidgetListener
{
[Register ("onWidgetChanged", "(Ljava/lang/String;)V", "GetOnWidgetChanged_Ljava_lang_String_Handler:UserApp.Interfaces.IWidgetListenerInvoker")]
void OnWidgetChanged (string? value);
}

[Register ("com/example/userapp/IWidgetListener", DoNotGenerateAcw = true)]
internal sealed class IWidgetListenerInvoker : Java.Lang.Object, IWidgetListener
{
public IWidgetListenerInvoker (IntPtr handle, JniHandleOwnership transfer)
: base (handle, transfer)
{
}

public void OnWidgetChanged (string? value)
{
}
}
}

namespace UserApp.AbstractWidgets
{
[Register ("com/example/userapp/AbstractWidget")]
public abstract class AbstractWidget : Java.Lang.Object
{
protected AbstractWidget (IntPtr handle, JniHandleOwnership transfer)
: base (handle, transfer)
{
}

[Register ("performAction", "()V", "GetPerformActionHandler")]
public abstract void PerformAction ();
}

[Register ("com/example/userapp/AbstractWidget", DoNotGenerateAcw = true)]
internal sealed class AbstractWidgetInvoker : AbstractWidget
{
public AbstractWidgetInvoker (IntPtr handle, JniHandleOwnership transfer)
: base (handle, transfer)
{
}

public override void PerformAction ()
{
}
}
}

namespace UserApp.Nested
{
[Register ("com/example/userapp/OuterClass")]
Expand Down
Loading