Skip to content

Commit 6bf41d5

Browse files
committed
Context: dotnet/java-interop#1077 Context: dotnet/java-interop@1f27ab5 Context: f6f11a5 [Desugaring][0] is the process of rewriting Java bytecode so that Java 8+ constructs can be used on Android pre-7.0 (API-24), as API-24 is the Android version which added native support for Java 8 features such as [interface default methods][1]. One of the implications of desugaring is that methods can "move"; consider this Java interface: package example; public interface StaticMethodsInterface { static int getValue() { return 3; } } Java.Interop bindings will attempt to invoke the `getValue().I` method on the type `example/StaticMethodsInterface`: public partial interface IStaticMethodsInterface : IJavaObject, IJavaPeerable { private static readonly JniPeerMembers _members = new XAPeerMembers ("example/StaticMethodsInterface", typeof (IStaticMethodsInterface), isInterface: true); static unsafe int Value { get { const string __id = "getValue.()I"; var __rm = _members.StaticMethods.InvokeInt32Method (__id, null); return __rm; } } } The problem is that, after Desugaring, the Java side *actually* looks like this: package example; public interface StaticMethodsInterface { } public class StaticMethodsInterface$-CC { public static int getValue() {return 3;} } Commits dotnet/java-interop@1f27ab55 and f6f11a5 added partial runtime support for this scenario via `AndroidTypeManager.GetStaticMethodFallbackTypesCore()`, which would attempt to lookup types with a `$-CC` suffix. While this was a good start, it wasn't ever actually *tested* end-to-end. Consequently, instead of *working*, this would instead cause the process to *abort*: JNI DETECTED ERROR IN APPLICATION: can't call static int example.StaticMethodsInterface$-CC.getValue() with class java.lang.Class<example.StaticMethodsInterface> in call to CallStaticIntMethodA from void crc….MainActivity.n_onCreate(android.os.Bundle) Oops. dotnet/java-interop#1077 improves our runtime support for invoking *`static`* methods on interfaces. Add a new `XASdkDeployTests.SupportDesugaringStaticInterfaceMethods()` test which performs an on-device, end-to-end invocation of a static method on a Java interface, to ensure that things *actually* work. *Note*: if `$(SupportedOSPlatformVersion)` is 24 or higher, this test will work even without dotnet/java-interop#1077, as Desugaring is *disabled* in that case. [0]: https://developer.android.com/studio/write/java8-support#library-desugaring [1]: https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
1 parent 6a310b8 commit 6bf41d5

File tree

6 files changed

+68
-3
lines changed

6 files changed

+68
-3
lines changed

.gitmodules

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
branch = main
1313
[submodule "external/Java.Interop"]
1414
path = external/Java.Interop
15-
url = https://github.com/xamarin/java.interop.git
16-
branch = main
15+
url = https://github.com/jonpryor/java.interop.git
16+
branch = jonp-use-right-class-for-remapped-static-method-invocations
1717
[submodule "external/lz4"]
1818
path = external/lz4
1919
url = https://github.com/lz4/lz4.git

external/Java.Interop

Submodule Java.Interop updated 22 files

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/ResourceData.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ static class ResourceData
2121
static Lazy<byte []> javaSourceTestInterface = new Lazy<byte []> (() => GetResourceData ("JavaSourceTestInterface.java"));
2222
static Lazy<byte []> remapActivityJava = new Lazy<byte []> (() => GetResourceData ("RemapActivity.java"));
2323
static Lazy<byte []> remapActivityXml = new Lazy<byte []> (() => GetResourceData ("RemapActivity.xml"));
24+
static Lazy<byte []> idmStaticMethodsInterface = new Lazy<byte []> (() => GetResourceData ("StaticMethodsInterface.java"));
2425

2526
public static byte[] JavaSourceJarTestJar => javaSourceJarTestJar.Value;
2627
public static byte[] JavaSourceJarTestSourcesJar => javaSourceJarTestSourcesJar.Value;
@@ -34,6 +35,7 @@ static class ResourceData
3435
public static string JavaSourceTestInterface => Encoding.ASCII.GetString (javaSourceTestInterface.Value);
3536
public static string RemapActivityJava => Encoding.UTF8.GetString (remapActivityJava.Value);
3637
public static string RemapActivityXml => Encoding.UTF8.GetString (remapActivityXml.Value);
38+
public static string IdmStaticMethodsInterface => Encoding.UTF8.GetString (idmStaticMethodsInterface.Value);
3739

3840
static byte[] GetResourceData (string name)
3941
{

tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
<EmbeddedResource Include="Resources\RemapActivity*">
3232
<LogicalName>%(FileName)%(Extension)</LogicalName>
3333
</EmbeddedResource>
34+
<EmbeddedResource Include="Resources\StaticMethodsInterface*">
35+
<LogicalName>%(FileName)%(Extension)</LogicalName>
36+
</EmbeddedResource>
3437
</ItemGroup>
3538

3639
<ItemGroup>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package example;
2+
3+
public interface StaticMethodsInterface {
4+
static int getValue() {
5+
return 3;
6+
}
7+
}

tests/MSBuildDeviceIntegration/Tests/XASdkDeployTests.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,59 @@ public void TypeAndMemberRemapping ([Values (false, true)] bool isRelease)
155155
);
156156
}
157157

158+
[Test]
159+
public void SupportDesugaringStaticInterfaceMethods ()
160+
{
161+
AssertHasDevices ();
162+
if (!Builder.UseDotNet) {
163+
Assert.Ignore ("Skipping. Test not relevant under Classic.");
164+
}
165+
166+
var proj = new XASdkProject () {
167+
IsRelease = true,
168+
OtherBuildItems = {
169+
new AndroidItem.AndroidJavaSource ("StaticMethodsInterface.java") {
170+
Encoding = new UTF8Encoding (encoderShouldEmitUTF8Identifier: false),
171+
TextContent = () => ResourceData.IdmStaticMethodsInterface,
172+
Metadata = {
173+
{ "Bind", "True" },
174+
},
175+
},
176+
},
177+
};
178+
179+
// Note: To properly test, Desugaring must be *enabled*, which requires that
180+
// `$(SupportedOSPlatformVersion)` be *less than* 23. 21 is currently the default,
181+
// but set this explicitly anyway just so that this implicit requirement is explicit.
182+
proj.SetProperty (proj.ReleaseProperties, "SupportedOSPlatformVersion", "21");
183+
184+
proj.MainActivity = proj.DefaultMainActivity.Replace ("//${AFTER_ONCREATE}", @"
185+
Console.WriteLine ($""# jonp static interface default method invocation; IStaticMethodsInterface.Value={Example.IStaticMethodsInterface.Value}"");
186+
");
187+
proj.SetRuntimeIdentifier (DeviceAbi);
188+
var relativeProjDir = Path.Combine ("temp", TestName);
189+
var fullProjDir = Path.Combine (Root, relativeProjDir);
190+
TestOutputDirectories [TestContext.CurrentContext.Test.ID] = fullProjDir;
191+
var files = proj.Save ();
192+
proj.Populate (relativeProjDir, files);
193+
proj.CopyNuGetConfig (relativeProjDir);
194+
var dotnet = new DotNetCLI (proj, Path.Combine (fullProjDir, proj.ProjectFilePath));
195+
196+
Assert.IsTrue (dotnet.Build (), "`dotnet build` should succeed");
197+
Assert.IsTrue (dotnet.Run (), "`dotnet run` should succeed");
198+
199+
bool didLaunch = WaitForActivityToStart (proj.PackageName, "MainActivity",
200+
Path.Combine (fullProjDir, "logcat.log"));
201+
Assert.IsTrue (didLaunch, "MainActivity should have launched!");
202+
var logcatOutput = File.ReadAllText (Path.Combine (fullProjDir, "logcat.log"));
203+
204+
StringAssert.Contains (
205+
"IStaticMethodsInterface.Value=3",
206+
logcatOutput,
207+
"Was IStaticMethodsInterface.Value executed?"
208+
);
209+
}
210+
158211
[Test]
159212
[Category ("Debugger"), Category ("Node-4")]
160213
public void DotNetDebug ([Values("net6.0-android", "net7.0-android")] string targetFramework)

0 commit comments

Comments
 (0)