Merge dotnet/java-interop into external/Java.Interop with full history#11744
Merge dotnet/java-interop into external/Java.Interop with full history#11744jonathanpeppers wants to merge 1507 commits into
Conversation
Context: e10ba7b Context: https://stackoverflow.com/questions/55853220/handling-change-in-newlines-by-xml-transformation-for-cdata-from-java-8-to-java Context: https://bugs.java.com/bugdatabase/view_bug?bug_id=8223291 **Background**: Since commit d0996b0, *two* JDKs were required in order to *fully* build and test Java.Interop: JDK 1.8 and JDK-11. This is because `src/Java.Interop` requires JDK-11+ to build, while some unit tests required JDK 1.8 to pass (because Java XML output changed between JDK 1.8 and JDK-11; see also e10ba7b). Recently, a question arose: how well does .NET Android work with JDK 17? ([Android Studio recently bumped][0] the bundled JDK from JDK-11 to JDK-17.) The "straightforward" approach of "provision JDK-17 and just build everything with JDK-17" quickly meant that Java.Interop needed to build under JDK-17. This in turn segued into a "how do I make the MSBuild property meanings clearer", as `$(JavaCPath)` would be for JDK 1.8, while `$(JavaC11Path)` was for JDK-11, but with JDK-17 being provisioned `$(JavaC11Path)` *actually* was for JDK-17, which is just confusing. After discussion, we decided that we don't need to continue using JDK 1.8 anymore. Android API-31 requires JDK-11 in order to use various Android SDK build tools, and the Google Play Store requires a target SDK version of API-33 starting 2023-Aug. There is not much point in maintaining JDK 1.8 support. JDK 11 or later is now required. Update to use Gradle 8.1.1. This is needed for later JDK-17 support. `java/util/Collection.java` existed to help test API documentation import (when `$ANDROID_SDK_PATH` is set). JDK-11 does not support compiling `java/util/Collection.java` anymore; it errors out with: java/java/util/Collection.java:1: error: package exists in another module: java.base package java.util; ^ "Rename" this type to `android/animation/TypeEvaluator.java`, and update the API documentation import tests accordingly. Update the `ExpectedTypeDeclaration.MajorVersion` values to 0x37. The `.class` files for nested types has seen the addition of a new `NestHost` constant; see also: * https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.28 * https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-5.html#jvms-5.4.4 Update `ExpectedTypeDeclaration.ConstantPoolCount` as appropriate. Additional `NestHost`-related fallout is that `JavaType.class` now includes `com/xamarin/JavaType$RNC$RPNC` in the `InnerClasses` table. Update `tools/java-source-utils` unit tests so that JDK-11 can now be used to run (and pass!) the unit tests. (This previously required JDK 1.8.) [0]: https://web.archive.org/web/20230507035529/https://developer.android.com/studio/releases/#jdk-17
Context: a87a90d Context: ff1855f Context: 835c59b .NET Android does not support `generator --codegen-target=XamarinAndroid`, because we wanted .NET Android to support binding Java Interface Default Methods by default, which in turn requires using `JniPeerMembers` and C# support static fields within `interface`s. Now that we have removed support for Classic Xamarin.Android (ff1855f), we can begin to remove Classic-only features. The changes to `generator` aren't very interesting, however some changes were required in our test suites: * Remove `XamarinAndroid` tests * Update `Integration-Tests` to move shared test inputs like `Adapters.xml` from `expected` to `expected-ji` TODO: turn XA4232 into an error, not a warning.
…2: Build ID 7873259 (#1122)
Context: dd041d2 When multiple JDK's are found, a JDK 8 might be chosen. Specify `JdkInfo.MinimumJdkVersion="11"` to ensure we use JDK-11 or newer.
Changes: dotnet/android-tools@44885bc...3cee10b * dotnet/android-tools@3cee10b: Bump LibZipSharp to 3.0.0 (dotnet/android-tools#210) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
There are several warnings when building `Mono.Android.dll` related
to `[Obsolete]` methods, e.g.:
warning CS0672: Member 'MediaRouteActionProvider.OnCreateActionView()' overrides obsolete member 'ActionProvider.OnCreateActionView()'.
Add the Obsolete attribute to 'MediaRouteActionProvider.OnCreateActionView()'
Technically, `MediaRouteActionProvider.onCreateActionView()` is only
marked as `deprecated` in the [docs][0], but not in `android.jar`:
public class MediaRouteActionProvider extends ActionProvider {
public View onCreateActionView() {
throw new RuntimeException("Stub!");
}
}
Regardless, any method that overrides a deprecated method should
itself be marked as deprecated.
Another case specific to `Mono.Android.dll` is
[`ContextWrapper.setWallpaper()`][1]. This method is marked as
`deprecated-since`=23, but the base method is marked as
`deprecated-since`=16. This causes us to generate:
public class Context {
[Obsolete]
public virtual void SetWallpaper () { ... }
}
public class ContextWrapper : Context {
[ObsoletedOSPlatform ("android23.0")]
public override void SetWallpaper () { ... }
}
This causes the same CS0672 warning.
Fix the CS0672 warnings by setting the `override` method's
`deprecated-since` to match the base method's `deprecated-since`.
[0]: https://developer.android.com/reference/android/app/MediaRouteActionProvider?hl=en#onCreateActionView()
[1]: https://developer.android.com/reference/android/content/ContextWrapper?hl=en#setWallpaper(android.graphics.Bitmap)
Parsing of `<a/>` elements would occasionally fail when they didn't
match our expectations/requirements:
* Unquoted URLs, a'la `android/database/sqlite/SQLiteDatabase.java`:
* <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
Resulting in:
System.Xml.XmlException: 'https' is an unexpected token. The expected token is '"' or '''. Line 1, position 11.
or a'la `java/io/PipedOutputStream.java`
* @exception IOException if the pipe is <a href=#BROKEN> broken</a>,
resulting in:
System.Xml.XmlException: '#' is an unexpected token. The expected token is '"' or '''. Line 1, position 11.
* Improperly quoted attributes, a'la `android/telephony/PhoneNumberUtils.java`:
* Matching is based on <a href="https://github.com/google/libphonenumber>libphonenumber</a>.
Resulting in:
System.Xml.XmlException: '<', hexadecimal value 0x3C, is an invalid attribute character. Line 1, position 67.
* Use of "raw" `&`, a'la `android/widget/ProgressBar.java`:
* <a href="https://material.io/guidelines/components/progress-activity.html#progress-activity-types-of-indicators">
* Progress & activity</a>.
Resulting in:
System.Xml.XmlException: An error occurred while parsing EntityName. Line 2, position 11.
Fix this by updating updating the `InlineHyperLinkOpenTerm` terminal
to *not* require `href`, and updating the `InlineHyperLinkDeclaration`
rule to better deal with whatever chaos is there.
When we encounter an `<a/>` element that points to code or a local
path we will now only include the element value in the javadoc, and
not the full `href` attribute value.
Replace the `IgnorableDeclaration` rule with an
`IgnorableCharTerminal` terminal. This better supports `@` in the
content stream when it's not part of a Javadoc inline tag, e.g.
`<a href="mailto:nobody@google.com">nobody</a>`.
Fixes: dotnet/java-interop#1071 The latest API docs update contained a couple dozen parsing issues due to `<code/>` parsing, including: * Closing element doesn't match opening element: `<code>null</null>` * Content including `@`: `<code>android:label="@string/resolve_title"</code>` * Closing element is actually an opening element: `<code>Activity.RESULT_OK<code>` * Improper element nesting: `<code><pre><p>content</code></pre></p>` * Use of attributes: `<code class=prettyprint>content<code>` Fix this by replacing `CodeElementDeclaration` to use a new `CodeElementContentTerm` terminal, which is a "greedy regex" which grabs `<code` until one of: * `</code>` * `</null>` * `<code>` The result of `CodeElementDeclaration` is the end of the `<code>` element until the beginning of one of the above terminators: * `<code>null</null>` becomes `<c>null</c>` * `<code>android:label="@string/resolve_title"</code>` becomes `<c>android:label="@string/resolve_title"</c>`.` * `<code>Activity.RESULT_OK<code>` becomes `<c>Activity.RESULT_OK</c>`. * `<code><pre><p>content</code></pre></p>` becomes the mess `<c><pre><p>some content</c></pre></p>` 🤷♂️ * `<code class=prettyprint>content<code>` becomes `<c>content</c>`.`
Context: a8d50a5 Context: ff1855f As part of the Nullable Reference Type work in ff1855f, we added a BG8A08 warning when the `//*/@path` attribute of a "metadata" element is not provided. However, metadata files can also contain `<ns-replace/>` elements, which do not have a `@path` attribute, and thus erroneously trigger this warning: generated\msbuild-metadata.xml(3,4): warning BG8A08: Metadata.xml element '<ns-replace source="com.google.androidx" replacement="Xamarin.AndroidX" />' is missing the 'path' attribute. Skip running any of the standard "metadata" logic when we hit a `<ns-replace>` element, as they are handled elsewhere.
…1137) As we consume nightly .NET 8 builds, they sometimes depend on nightly .NET 7 builds. One error you can run into is: error NU1102: Unable to find package Microsoft.AspNetCore.App.Ref with version (= 7.0.11) error NU1102: Unable to find package Microsoft.WindowsDesktop.App.Ref with version (= 7.0.11) For projects that are not even ASP.NET or Windows desktop apps! To even be able to access these feeds, they would need to be an entry within `NuGet.config` similar to <packageSources> <clear/> <add key="darc-pub-dotnet-aspnetcore-[SHA]" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-aspnetcore-[SHA]/nuget/v3/index.json" /> <add key="darc-pub-dotnet-windowsdesktop-[SHA]" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-windowsdesktop-[SHA]/nuget/v3/index.json" /> </packageSources> We don't currently track these packages, because we don't actually use them. The .NET SDK team has provided a setting to workaround this, [`$(DisableTransitiveFrameworkReferenceDownloads)`][0], we have been [using in xamarin/xamarin-android for some time][1]. Let's do the same here to avoid this problem as seen in 4f9dbce6. [0]: https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#disabletransitiveframeworkreferencedownloads [1]: https://github.com/xamarin/xamarin-android/blob/6768c731d327c8148c45304c895ca8987a9cc2f1/Directory.Build.props#L26-L27
…1138) Context: #8279 This reverts commit 83b5089. Near the end of .NET 8 RC 1, the .NET 8 SDK depends on nightly packages for .NET 6 and .NET 7: * dotnet/runtime 7.0.11 * dotnet/runtime 6.0.22 These come from feeds within `NuGet.config` such as: <packageSources> <clear/> <!-- Added manually for dotnet/runtime 7.0.11 --> <add key="darc-pub-dotnet-runtime-a2ad4f0" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-runtime-a2ad4f03/nuget/v3/index.json" /> <!-- Added manually for dotnet/runtime 6.0.22 --> <add key="darc-pub-dotnet-runtime-762f437" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-runtime-762f4379/nuget/v3/index.json" /> </packageSources> The version number is stable, so the .NET releng team has infrastructure to create a new feed per git commit. Unfortunately, this makes NuGet central package management unusable for us. 😢 As seen in #8279, we get errors like: Package source mapping matches found for package ID 'Microsoft.NETCore.App.Ref' are: 'dotnet-public'. … external\Java.Interop\src\Java.Interop.Tools.Expressions\Java.Interop.Tools.Expressions.csproj error NU1102: Unable to find package Microsoft.NETCore.App.Ref with version (= 7.0.11) - Found 83 version(s) in dotnet-public [ Nearest version: 8.0.0-preview.1.23110.8 ] - Versions from dotnet-eng were not considered [Xamarin.Android.sln] This is because it can only possibly resolve this package from `dotnet-public`: <packageSourceMapping> <packageSource key="dotnet-public"> <package pattern="*" /> </packageSource> </packageSourceMapping> Because 7.0.11 hasn't shipped yet, it is on neither NuGet.org nor `dotnet-public`. For this to work, we would somehow need this `NuGet.config` fragment to be added to the xamarin/java.interop repo whenever xamarin/xamarin-android gets a newer .NET 8 SDK: <packageSourceMapping> <packageSource key="darc-pub-dotnet-runtime-[HASH]"> <package pattern="Microsoft.NETCore.App.Ref" /> </packageSource> </packageSourceMapping> For now, let's revert 83b5089. Maybe there is some solution we can come up with to use this in the future.
Update `<JdkInfo/>` task to emit new `$(Java*MajorVersion)`
and `$(JavaApi*DefineConstants)` MSBuild properties. These are used
by `src/Java.Base` so that it knows which JDK version it's binding.
Update `src/Java.Base` to support binding the `java.base.jmod` from
JDK 17.
Note: This "JDK-17 Java.Base binding" was a "time limited" effort.
To build against JDK-17:
1. Install JDK-17.
2. Prepare and override `$(JdksRoot)`:
dotnet build -t:Prepare Java.Interop.sln -p:JdksRoot=/Library/Java/JavaVirtualMachines/microsoft-17.jdk/Contents/Home
will use the Microsoft OpenJDK 17 installation on macOS.
3. Build:
dotnet build Java.Interop.sln
Changes: dotnet/android-tools@3cee10b...9c50a2d * dotnet/android-tools@9c50a2d: [build] set `$(DisableTransitiveFrameworkReferenceDownloads)`=true (dotnet/android-tools#216) * dotnet/android-tools@52f0866: [Xamarin.Android.Tools.AndroidSdk] Check all <intent-filter/>s (dotnet/android-tools#214) * dotnet/android-tools@57be026: [Xamarin.Android.Tools.AndroidSdk] Update SDK component for API-34 (dotnet/android-tools#211) * dotnet/android-tools@0a9ea47: [Xamarin.Android.Tools.AndroidSdk] Add API-34 to KnownVersions (dotnet/android-tools#212) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Context: https://discord.com/channels/732297728826277939/732297837953679412/1151531791069499524 A user reported the following output in their `java-resolution-report.log`: The field 'Java.Interop.Tools.JavaTypeSystem.Models.JavaFieldModel' was removed because its name contains a dollar sign. The class '[Class] com.google.android.libraries.navigation.internal.aac.ad' was removed because the Java base type 'com.google.android.libraries.navigation.internal.aad.ar<com.google.android.libraries.navigation.internal.aac.af<K, V>>' could not be found. We should be providing the user with the name of the removed field rather than the `JavaFieldModel` type name. To do this, add an appropriate `JavaFieldModel.ToString ()` method override.
Changes: dotnet/android-tools@9c50a2d...8a971d9 * dotnet/android-tools@8a971d9: Merge pull request dotnet/android-tools#217 from xamarin/dev/tondat/main-openjdkms * dotnet/android-tools@42bbef8: Update OpenJDK location for OpenJDK17 on windows Adds support to look for JDK installation on Windows within `%ProgramFiles%\Android\openjdk\jdk-*`. Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Context: #8360 Context: c1ff50a Context: f91da28 Context: dotnet/android-tools@34e98e2 When .NET 8 RC 2 took a dependency on dotnet/runtime 7.0.12 and 6.0.23, we added `external/xamarin-android-tools.override.props` in #8360 with the contents: <Project> <PropertyGroup> <RestoreAdditionalProjectSources> https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-runtime-26e0f822/nuget/v3/index.json; https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-runtime-301ba1ee/nuget/v3/index.json; </RestoreAdditionalProjectSources> </PropertyGroup> </Project> This allowed xamarin-android/external/xamarin-android-tools to find and use the new NuGet sources, but xamarin-android/external/Java.Interop uses its own checkout of xamarin-android-tools in `xamarin-android/external/java.interop/external/xamarin-android-tools`.` This led to the error during the `prepare java.interop Debug` stage: tests/api-compatibility/api-compatibility.targets(3,3): warning MSB4011: "Configuration.props" cannot be imported again. It was already imported at "build-tools/scripts/RunTests.targets (7,3)". This is most likely a build authoring error. This subsequent import will be ignored. external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: Unable to find package Microsoft.NETCore.App.Ref with version (= 6.0.23) external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: - Found 86 version(s) in dotnet-public [ Nearest version: 7.0.0-preview.1.22076.8 ] external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: - Found 1 version(s) in dotnet-eng [ Nearest version: 5.0.0-alpha.1.19618.1 ] external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: Unable to find package Microsoft.NETCore.App.Ref with version (= 6.0.23) external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: - Found 86 version(s) in dotnet-public [ Nearest version: 7.0.0-preview.1.22076.8 ] external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: - Found 1 version(s) in dotnet-eng [ Nearest version: 5.0.0-alpha.1.19618.1 ] build-tools/scripts/DotNet.targets(19,5): error MSB3073: The command ""bin/Release/dotnet/dotnet" build -t:Prepare Java.Interop.sln -c Debug -p:JdksRoot= -p:DotnetToolPath=bin/Release/dotnet/dotnet -bl:build-tools/scripts/../../bin/BuildDebug/msbuild-20230925T183954-prepare-java-interop.binlog" exited with code 1. 1 Warning(s) 7 Error(s) Introduce an `external/xamarin-android-tools.override.props` within Java.Interop which imports `..\Directory.Build.props`. This allows MSBuild properties to "flow" from a "parent" `xamarin-android/external/Java.Interop.override.props` through to `xamarin-android/external/Java.Interop/external/xamarin-android-tools`, allowing a xamarin-android checkout to more easily control MSBuild properties used by xamarin-android-tools.
…1143) Fixes: dotnet/java-interop#1142 Context: b116a4b In b116a4b, we added a feature to automatically mark a method as "deprecated" if it overrides a "deprecated" method. However, there can be instances where this is undesirable for a user, and there is currently no way to opt out of this change on a global or `metadata` level. Add a global opt-out for this feature, usable via `generator --lang-features=do-not-fix-obsolete-overrides …`. This will eventually be made available to users via an MSBuild property in a separate xamarin/xamarin-android PR.
Fixes: #8337 A customer's app with the code: SetContentView(Resource.Layout.activity_main); FindViewById<Button>(Resource.Id.asd).Click += MainActivity_Click; Crashes with `-c Release -p:AndroidLinkMode=r8` with: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.companyname.New_folder/crc64abe8cc9139195b67.MainActivity}: java.lang.ClassNotFoundException: android.view.View_IOnClickListenerImplementor at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3644) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3781) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:101) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:138) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2306) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7918) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) Caused by: java.lang.ClassNotFoundException: android.view.View_IOnClickListenerImplementor at crc64abe8cc9139195b67.MainActivity.n_onCreate(Native Method) at crc64abe8cc9139195b67.MainActivity.onCreate(MainActivity.java:30) at android.app.Activity.performCreate(Activity.java:8342) at android.app.Activity.performCreate(Activity.java:8321) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1417) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3625) ... 12 more Which, can be solved by adding your own `proguard` rules like: -keep class android.view.View_IOnClickListenerImplementor { *; } In a8cbe01, we thought (incorrectly): > Additionally, stop emitting the `[Register]` attribute for > `*Implementor` classes: > > [global::Android.Runtime.Register ("mono/android/view/View_OnFocusChangeListenerImplementor")] > partial class IOnFocusChangeListenerImplementor {/* … */} > > The `[Register]` attribute is not needed, because `*Implementor` > classes are generated internal implementation details. `proguard_xamarin.cfg` has the entry: -keep class mono.android.** { *; <init>(...); } We could do `-keep class android.**` [^0], but that would certainly preserve way too much! For now, let's just restore `[Register]` to revisit this issue at a later date -- maybe .NET 9? [^0]: Why is `View_IOnClickListenerImplementor` in the `android.view` package?! Because package name generation for Java Callable Wrappers is special-cased for `Mono.Android` to simply [lowercase the namespace name][0], which applies only if the type *doesn't* have `[RegisterAttribute]`! [0]: https://github.com/xamarin/java.interop/blob/009f9c03317d1ef0c0a66fffe2b5c654cb9fd2e1/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs#L200-L209
Context: 59c5d93 Context: https://discord.com/channels/732297728826277939/732297837953679412/1162418256615846030 Context: https://discord.com/channels/732297728826277939/732297837953679412/1162422742256205954 A customer reports that when binding [androidx.emoji2.emoji2-emojipicker-1.4.0][0], [`EmojiPickerView.setOnEmojiPickedListener()`][1] wasn't being bound. Subsequent investigation showed that it wasn't being bound because it had `//method[@visibility="kotlin-internal"]`. …but *why* did `class-parse` indicate that `EmojiPickerView.setOnEmojiPickedListener()` has visibility of `kotlin-internal` and not `public`? In order to assist this question, update `class-parse -dump` to also dump out the parsed Kotlin metadata blob. Example: % dotnet …/class-parse.dll -dump EmojiPickerView.class … Kotlin Class Metadata [1.8.0]: { "$id": "1", "CompanionObjectName": "Companion", "Constructors": { ... }, "EnumEntries": { "$id": "14", "$values": [] }, "Flags": 6, "FullyQualifiedName": null, "Inheritability": 0, "NestedClassNames": { "$id": "15", "$values": [ "Companion" ] }, "ObjectType": 0, "SealedSubclassFullyQualifiedNames": null, "SuperTypeIds": null, "SuperTypes": { "$id": "16", "$values": [ { "$id": "17", "Arguments": { "$id": "18", "$values": [] }, "Nullable": false, "FlexibleTypeCapabilitiesId": null, "FlexibleUpperBound": null, "FlexibleUpperBoundId": 0, "ClassName": "android/widget/FrameLayout;", "TypeParameter": null, "TypeParameterName": null, "TypeAliasName": null, "OuterType": null, "OuterTypeId": null, "AbbreviatedType": null, "AbbreviatedTypeId": null, "Flags": 0 } ] }, "TypeParameters": { "$id": "19", "$values": [] }, "VersionRequirements": null, "Visibility": 3, "Functions": { "$id": "20", "$values": [ ... { "$id": "145", "Name": "setOnEmojiPickedListener", "JvmName": "setOnEmojiPickedListener", "JvmSignature": null, "Flags": 6, "ReturnType": { "$id": "146", "Arguments": { "$id": "147", "$values": [] }, "Nullable": false, "FlexibleTypeCapabilitiesId": null, "FlexibleUpperBound": null, "FlexibleUpperBoundId": 0, "ClassName": "kotlin/Unit", "TypeParameter": null, "TypeParameterName": null, "TypeAliasName": null, "OuterType": null, "OuterTypeId": null, "AbbreviatedType": null, "AbbreviatedTypeId": null, "Flags": 0 }, "ReturnTypeId": 0, "TypeParameters": { "$id": "148", "$values": [] }, "ReceiverType": null, "ReceiverTypeId": 0, "TypeTable": null, "Contract": null, "ValueParameters": { "$id": "149", "$values": [ { "$id": "150", "Flags": 0, "Name": "onEmojiPickedListener", "Type": { "$id": "151", "Arguments": { "$id": "152", "$values": [ { "$id": "153", "Projection": 2, "Type": { "$id": "154", "Arguments": { "$id": "155", "$values": [] }, "Nullable": false, "FlexibleTypeCapabilitiesId": null, "FlexibleUpperBound": null, "FlexibleUpperBoundId": 0, "ClassName": "androidx/emoji2/emojipicker/EmojiViewItem;", "TypeParameter": null, "TypeParameterName": null, "TypeAliasName": null, "OuterType": null, "OuterTypeId": null, "AbbreviatedType": null, "AbbreviatedTypeId": null, "Flags": 0 }, "TypeId": 0 } ] }, "Nullable": true, "FlexibleTypeCapabilitiesId": null, "FlexibleUpperBound": null, "FlexibleUpperBoundId": 0, "ClassName": "androidx/core/util/Consumer;", "TypeParameter": null, "TypeParameterName": null, "TypeAliasName": null, "OuterType": null, "OuterTypeId": null, "AbbreviatedType": null, "AbbreviatedTypeId": null, "Flags": 0 }, "TypeId": 0, "VarArgElementType": null, "VarArgElementTypeId": 0 } ] }, "VersionRequirements": null }, ... }, "Properties": {... }, "TypeAliases": { "$id": "230", "$values": [] }, "TypeTable": null, "VersionRequirementTable": { "$id": "231", "Requirements": { "$id": "232", "$values": [ { "$id": "233", "Version": 25, "VersionFull": 0, "Level": 1, "ErrorCode": 0, "Message": 0, "VersionKind": 0 } ] } } } Kotlin Metadata String Table: [ "Landroidx/emoji2/emojipicker/EmojiPickerView;", "Landroid/widget/FrameLayout;", "context", "Landroid/content/Context;", "attrs", "Landroid/util/AttributeSet;", "defStyleAttr", "", "(Landroid/content/Context;Landroid/util/AttributeSet;I)V", "_emojiGridRows", "", "Ljava/lang/Float;", "bodyAdapter", "Landroidx/emoji2/emojipicker/EmojiPickerBodyAdapter;", "value", "emojiGridColumns", "getEmojiGridColumns", "()I", "setEmojiGridColumns", "(I)V", "emojiGridRows", "getEmojiGridRows", "()F", "setEmojiGridRows", "(F)V", "emojiPickerItems", "Landroidx/emoji2/emojipicker/EmojiPickerItems;", "onEmojiPickedListener", "Landroidx/core/util/Consumer;", "Landroidx/emoji2/emojipicker/EmojiViewItem;", "recentEmojiProvider", "Landroidx/emoji2/emojipicker/RecentEmojiProvider;", "recentItemGroup", "Landroidx/emoji2/emojipicker/ItemGroup;", "recentItems", "", "Landroidx/emoji2/emojipicker/EmojiViewData;", "recentNeedsRefreshing", "", "scope", "Lkotlinx/coroutines/CoroutineScope;", "stickyVariantProvider", "Landroidx/emoji2/emojipicker/StickyVariantProvider;", "addView", "", "child", "Landroid/view/View;", "params", "Landroid/view/ViewGroup$LayoutParams;", "index", "width", "height", "buildEmojiPickerItems", "buildEmojiPickerItems$emoji2_emojipicker_release", "createEmojiPickerBodyAdapter", "refreshRecent", "refreshRecent$emoji2_emojipicker_release", "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "removeAllViews", "removeView", "removeViewAt", "removeViewInLayout", "removeViews", "start", "count", "removeViewsInLayout", "setOnEmojiPickedListener", "setRecentEmojiProvider", "showEmojiPickerView", "Companion", "emoji2-emojipicker_release" ] [0]: https://maven.google.com/web/index.html#androidx.emoji2:emoji2-emojipicker:1.4.0 [1]: https://developer.android.com/develop/ui/views/text-and-emoji/emoji-picker#how-use
Fixes: dotnet/java-interop#1139 Context: https://jetbrains.gitbooks.io/kotlin-reference-for-kindle/content/properties.html Kotlin does not allow classes to explicitly contain fields: > Classes in Kotlin cannot have fields. They can *implicitly* contain fields, but not explicitly. Syntax that *look like* a field to those who don't know Kotlin: /* partial */ class EmojiPickerView { private var onEmojiPickedListener: Consumer<EmojiViewItem>? = null } are in fact *properties*, which may or may not involve a compiler- generated backing field. When a property is declared in Kotlin, Java `get*` and/or `set*` methods are generated as needed. In the case where the property is `internal` -- which is not supported by Java -- `public` getters or setters are generated. We wish to "hide" these as they are not intended to be part of the public API. That is, they would not be callable by Kotlin code. However, consider these code fragments from [`EmojiPickerView.kt`][0]: /* partial */ class EmojiPickerView { // Line 100 private var onEmojiPickedListener: Consumer<EmojiViewItem>? = null // Lines 317-319 fun setOnEmojiPickedListener(onEmojiPickedListener: Consumer<EmojiViewItem>?) { this.onEmojiPickedListener = onEmojiPickedListener } } Default member visibility in Kotlin is `public`, so this declares a private `onEmojiPickedListener` property and a public `setOnEmojiPickedListener()` method. However, we were incorrectly determining visibility (59c5d93): we treated `onEmojiPickedListener` and `setOnEmojiPickedListener()` as if they were part of the same property. As part of this association, we saw that `onEmojiPickedListener` was private, and marked `setOnEmojiPickedListener()` as private to follow suit. This was incorrect, because `private` properties do not have setters *at all*, so we should not attempt to hide anything for `private` properties, only for `internal` properties. With that incorrect association broken, that allows `setOnEmojiPickedListener()` to be considered separately, and found to have `public` visibility. For example, this Kotlin code: private var type = 0 internal var itype = 0 generates Java code equivalent to: private int type; private int itype; public final int getItype$main() { return this.itype; } public final void setItype$main(int <set-?>) { this.itype = <set-?>; Additionally, when matching `internal` properties to their getters or setters, the generated name differs from the names given to `public` getters and setters. // Kotlin: public var type = 0 // Java: public final int getType () { ... } // Kotlin: internal var type = 0 // Java: public final int getType$main () { ... } Fix this scenario: * Do not attempt to hide getters/setters for `private` Kotlin properties. * Improve matching of generated names for `internal` Kotlin properties. Additionally, our existing unit test in `NameShadowing.kt` was written incorrectly: // Incorrect, return type of a function fun setType(type: Int) = { println (type); } // Correct, return type of void fun setType(type: Int) { println (type); } The fixed `NameShadowing.kt` unit test fails without the other changes here. Additionally, add several more unit test cases to cover `internal` properties and mangled getter/setter names. Additionally, some enum values in `KotlinPropertyFlags` were specified incorrectly which has been fixed. [0]: https://github.com/androidx/androidx/blob/0d655214d339e006f4e13a85f55c78770c885f2e/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/EmojiPickerView.kt
#1145) Fixes: dotnet/java-interop#910 Context: d0996b0 Context: dotnet/java-interop#858 Consider the Java `java.lang.Runnable` interface: package java.lang; public interface Runnable { void run (); } This is bound as: package Java.Lang; public interface IRunnable : IJavaPeerable { void Run (); } with some slight differences depending on whether we're dealing with .NET Android (`generator --codegen-target=xajavainterop1`) or `src/Java.Base` (`generator --codegen-target=javainterop1`). Now, assume a Java API + corresponding binding which returns a `Runnable` instance: package example; public class Whatever { public static Runnable createRunnable(); } You can invoke `IRunnable.Run()` on the return value: IRunnable r = Whatever.CreateRunnable(); r.Run(); but how does that work? This works via an "interface Invoker", which is a class emitted by `generator` which implements the interface and invokes the interface methods through JNI: internal partial class IRunnableInvoker : Java.Lang.Object, IRunnable { public void Run() => … } Once Upon A Time™, the interface invoker implementation mirrored that of classes: a static `IntPtr` field held the `jmethodID` value, which would be looked up on first-use and cached for subsequent invocations: partial class IRunnableInvoker { static IntPtr id_run; public unsafe void Run() { if (id_run == IntPtr.Zero) id_run = JNIEnv.GetMethodID (class_ref, "run", "()V"); JNIEnv.CallVoidMethod (Handle, id_run, …); } } This approach works until you have interface inheritance and methods which come from inherited interfaces: package android.view; public /* partial */ interface ViewManager { void addView(View view, ViewGroup.LayoutParams params); } public /* partial */ interface WindowManager extends ViewManager { void removeViewImmediate(View view); } This would be bound as: namespace Android.Views; public partial interface IViewManager : IJavaPeerable { void AddView (View view, ViewGroup.LayoutParams @params); } public partial IWindowManager : IViewManager { void RemoveViewImmediate (View view); } internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager { static IntPtr id_addView; public void AddView(View view, ViewGroup.LayoutParams @params) { if (id_addView == IntPtr.Zero) id_run = JNIEnv.GetMethodID (class_ref, "addView", "…"); JNIEnv.CallVoidMethod (Handle, id_addView, …); } } Unfortunately, *invoking* `IViewManager.AddView()` through an `IWindowManagerInvoker` would crash! D/dalvikvm( 6645): GetMethodID: method not found: Landroid/view/WindowManager;.addView:(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V I/MonoDroid( 6645): UNHANDLED EXCEPTION: Java.Lang.NoSuchMethodError: Exception of type 'Java.Lang.NoSuchMethodError' was thrown. I/MonoDroid( 6645): at Android.Runtime.JNIEnv.GetMethodID (intptr,string,string) I/MonoDroid( 6645): at Android.Views.IWindowManagerInvoker.AddView (Android.Views.View,Android.Views.ViewGroup/LayoutParams) I/MonoDroid( 6645): at Mono.Samples.Hello.HelloActivity.OnCreate (Android.OS.Bundle) I/MonoDroid( 6645): at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (intptr,intptr,intptr) I/MonoDroid( 6645): at (wrapper dynamic-method) object.ecadbe0b-9124-445e-a498-f351075f6c89 (intptr,intptr,intptr) Interfaces are not classes, and this is one of the places that this is most apparent. Because of this crash, we had to use *instance* `jmethodID` caches: internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager { IntPtr id_addView; public void AddView(View view, ViewGroup.LayoutParams @params) { if (id_addView == IntPtr.Zero) id_run = JNIEnv.GetMethodID (class_ref, "addView", "…"); JNIEnv.CallVoidMethod (Handle, id_addView, …); } } Pro: no more crash! Con: *every different instance* of `IWindowManagerInvoker` needs to separately lookup whatever methods are invoked. There is *some* caching, so repeated calls to `AddView()` on the same instance will hit the cache, but if you obtain a different `IWindowManager` instance, `jmethodID` values will need to be looked up again. This was "fine", until dotnet/java-interop#858 enters the picture: interface invokers were full of Android-isms -- `Android.Runtime.JNIEnv.GetMethodID()`! `JNIEnv.CallVoidMethod()`! -- and thus ***not*** APIs that @jonpryor wished to expose within desktop Java.Base bindings. Enter `generator --lang-features=emit-legacy-interface-invokers`: when *not* specified, interface invokers will now use `JniPeerMembers` for method lookup and invocation, allowing `jmethodID` values to be cached *across* instances. In order to prevent the runtime crash, an interface may have *multiple* `JniPeerMembers` values, one per implemented interface, which is used to invoke methods from that interface. `IWindowManagerInvoker` now becomes: internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager { static readonly JniPeerMembers _members_android_view_ViewManager = …; static readonly JniPeerMembers _members_android_view_WindowManager = …; public void AddView(View view, ViewGroup.LayoutParams @params) { const string __id = "addView.…"; _members_android_view_ViewManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …); } public void RemoveViewImmediate(View view) { const string __id = "removeViewImmediate.…"; _members_android_view_WindowManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …); } } This has two advantages: 1. More caching! 2. Desktop `Java.Base` binding can now have interface invokers. Update `tests/generator-Tests` expected output. Note: to keep this patch smaller, JavaInterop1 output uses the new pattern, and only *some* XAJavaInterop1 tests use the new pattern. Added [CS0114][0] to `$(NoWarn)` in `Java.Base.csproj` to ignore warnings such as: …/src/Java.Base/obj/Debug-net7.0/mcw/Java.Lang.ICharSequence.cs(195,25): warning CS0114: 'ICharSequenceInvoker.ToString()' hides inherited member 'Object.ToString()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword. [Ignoring CS0114 is also done in `Mono.Android.dll` as well][1], so this is not a new or unique requirement. Update `Java.Interop.dll` so that `JniRuntime.JniValueManager.GetActivationConstructor()` now knows about and looks for `*Invoker` types, then uses the activation constructor from the `*Invoker` type when the source type is an abstract `class` or `interface`. Update `tests/Java.Base-Tests` to test for implicit `*Invoker` lookup and invocation support. ~~ Property Setters ~~ While testing on #8339, we hit this error (among others, to be addressed later): src/Mono.Android/obj/Debug/net8.0/android-34/mcw/Android.Views.IWindowInsetsController.cs(304,41): error CS0103: The name 'behavior' does not exist in the current context This was caused because of code such as: public partial interface IWindowInsetsController { public unsafe int SystemBarsBehavior { get { const string __id = "getSystemBarsBehavior.()I"; try { var __rm = _members_IWindowInsetsController.InstanceMethods.InvokeAbstractInt32Method (__id, this, null); return __rm; } finally { } } set { const string __id = "setSystemBarsBehavior.(I)V"; try { JniArgumentValue* __args = stackalloc JniArgumentValue [1]; __args [0] = new JniArgumentValue (behavior); _members_IWindowInsetsController.InstanceMethods.InvokeAbstractVoidMethod (__id, this, __args); } finally { } } } } This happened because when emitting the property setter, we need to update the `set*` method's parameter name to be `value` so that the normal property setter body is emitted properly. Update `InterfaceInvokerProperty.cs` so that the parameter name is set to `value`. ~~ Performance ~~ What does this do for performance? Add a new `InterfaceInvokerTiming` test fixture to `Java.Interop-PerformanceTests.dll`, which: 1. "Reimplements" the "legacy" and "JniPeerMembers" Invoker strategies 2. For each Invoker strategy: a. Invokes a Java method which returns a `java.lang.Runnable` instance b. Invokes `Runnable.run()` on the instance returned by (2.a) …100 times. c. Repeat (2.a) and (2.b) 100 times. The result is that using `JniPeerMembers` is *much* faster: % dotnet build tests/Java.Interop-PerformanceTests/*.csproj && \ dotnet test --logger "console;verbosity=detailed" bin/TestDebug-net7.0/Java.Interop-PerformanceTests.dll --filter "Name~InterfaceInvokerTiming" … Passed InterfaceInvokers [1 s] Standard Output Messages: ## InterfaceInvokers Timing: instanceIds: 00:00:01.1095502 ## InterfaceInvokers Timing: peerMembers: 00:00:00.1400427 Using `JniPeerMembers` takes ~1/8th the time as using `jmethodID`s. TODO: something is *probably* wrong with my test -- reviews welcome! -- as when I increase the (2.b) iteration count, the `peerMembers` time is largely unchanged (~0.14s), while the `instanceIds` time increases linearly. *Something* is wrong there. I'm not sure what. (Or *nothing* is wrong, and instance `jmethodID` are just *that* bad.) [0]: https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs0114 [1]: https://github.com/xamarin/xamarin-android/blob/d5c4ec09f7658428a10bbe49c8a7a3eb2f71cb86/src/Mono.Android/Mono.Android.csproj#L12C7-L12C7
Context: 5537aec In commit 5537aec, we updated the logic that matches a Kotlin public property getter/setter to a generated Java getter/setter that follows this pattern: // Kotlin public var type = 0 // Java private int type = 0; public int getType () { ... } public void setType (int p0) { ... } However, this caused unit tests in xamarin/xamarin-android to fail that when using Kotlin unsigned types: KotlinUnsignedTypesTests.cs: error CS0200: Property or indexer 'UnsignedInstanceMethods.UnsignedInstanceProperty' cannot be assigned to -- it is read only This is because properties that use Kotlin unsigned types append a `-<type-hash>` suffix to their getter/setter names: // Kotlin public var type: UInt = 0u // Java private int type = 0; public int getType-pVg5ArA () { ... } public void setType-WZ4Q5Ns (int p0) { ... } Update our Kotlin logic to handle this case.
Context: dotnet/java-interop#1153 [JNI][0] supports *two* modes of operation: 1. Native code creates the JVM, e.g. via [`JNI_CreateJavaVM()`][1] 2. The JVM already exists, and when Java code calls [`System.loadLibrary()`][3], the JVM calls the [`JNI_OnLoad()`][2] function on the specified library. Java.Interop samples and unit tests rely on the first approach, e.g. `TestJVM` subclasses `JreRuntime`, which is responsible for calling `JNI_CreateJavaVM()` so that Java code can be used. PR #1153 is exploring the use of [.NET Native AOT][4] to produce a native library which is used with Java-originated initialization. In order to make Java-originated initialization *work*, we need to be able to initialize `JniRuntime` and `JreRuntime` around existing JVM-provided pointers: * The `JavaVM*` provided to `JNI_OnLoad()`, which can be used to set `JniRuntime.CreationOptions.InvocationPointer`: [UnmanagedCallersOnly(EntryPoint="JNI_OnLoad")] int JNI_OnLoad(IntPtr vm, IntPtr reserved) { var options = new JreRuntimeOptions { InvocationPointer = vm, }; var runtime = options.CreateJreVM (); return runtime.JniVersion; return JNI_VERSION_1_6; } * The [`JNIEnv*` value provided to Java `native` methods][5] when they are invoked, which can be used to set `JniRuntime.CreationOptions.EnvironmentPointer`: [UnmanagedCallersOnly(EntryPoint="Java_example_Whatever_init")] void Whatever_init(IntPtr jnienv, IntPtr Whatever_class) { var options = new JreRuntimeOptions { EnvironmentPointer = jnienv, }; var runtime = options.CreateJreVM (); } Update `JniRuntime` and `JreRuntime` to support these Java-originated initialization strategies. In particular, don't require that `JreRuntimeOptions.JvmLibraryPath` be set, avoiding: System.InvalidOperationException: Member `JreRuntimeOptions.JvmLibraryPath` must be set. at Java.Interop.JreRuntime.CreateJreVM(JreRuntimeOptions builder) at Java.Interop.JreRuntime..ctor(JreRuntimeOptions builder) at Java.Interop.JreRuntimeOptions.CreateJreVM() [0]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html [1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#creating_the_vm [2]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Runtime.html#loadLibrary(java.lang.String) [3]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#JNJI_OnLoad [4]: https://learn.microsoft.com/dotnet/core/deploying/native-aot/ [5]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#native_method_arguments
Fixes: #7554 Placing `[Export]` on a constructor with parameters did not work as expected. Consider: public class Example : Java.Lang.Object { [Export(SuperArgumentsString = "")] public Example (int value) { this.value = value; } int value; } This *does* generate a Java Callable Wrapper with the desired constructor, but it *doesn't* invoke the correct C# constructor, because of the `TypeManager.Activate()` invocation: // Java JCW /* partial */ class Example extends java.lang.Object { public Example(int p0) { super (); if (getClass () == Example.class) { mono.android.TypeManager.Activate ( /* typeName */ "Namespace.Example, Assembly", /* signature */ "", /* instance */ this, /* parameterList */ new java.lang.Object[] { p0 }); } } } In particular, because `signature` is the empty string, `TypeManager.Activate()` will lookup and invoke the *default* constructor, rather than the `Example(int)` constructor. (This despite the fact that `parameterList` contains arguments!) Consequently, when Java invokes the `Example(int)` constructor, an exception is thrown, as the default constructor it wants doesn't exist: Could not activate JNI Handle 0x7ffd2bd94e50 (key_handle 0x65d856e) of Java type 'namespace/Example' as managed type 'Namespace.Example'. Java.Interop.JavaLocationException: Exception of type 'Java.Interop.JavaLocationException' was thrown. Java.Lang.Error: Exception of type 'Java.Lang.Error' was thrown. --- End of managed Java.Lang.Error stack trace --- java.lang.Error: Java callstack: at mono.android.TypeManager.n_activate(Native Method) at mono.android.TypeManager.Activate(TypeManager.java:7) at namespace.Example.<init>(Example.java:0) … Update `JavaCallableWrapperGenerator.AddConstructor()` so that the `managedParameters` value is provided to the `Signature` instance for the `[Export]` constructor. This value is then inserted as the `signature` parameter value, which will allow the correct constructor to be invoked: // Fixed Java JCW /* partial */ class Example extends java.lang.Object { public Example(int p0) { super (); if (getClass () == Example.class) { mono.android.TypeManager.Activate ( /* typeName */ "Namespace.Example, Assembly", /* signature */ "System.Int32, System.Runtime", /* instance */ this, /* parameterList */ new java.lang.Object[] { p0 }); } } }
Context: dotnet/java-interop#1153 Context: 58f41b8 Context: 16cd04f PR #1153 is exploring the use of [.NET Native AOT][0] to produce a native library which is used *within* a `java`-originated process: % java -cp … com/microsoft/hello_from_jni/App # launches NativeAOT-generated native lib, executes C# code… As NativeAOT has no support for `System.Reflection.Emit`, the only way for Java code to invoke managed code -- in a Desktop Java.Base environment! [^0] see 58f41b8 -- would be to pre-generate the required marshal methods via `jnimarshalmethod-gen`. This in turn requires updating `jcw-gen` to support the pre-existing `Java.Interop.JavaCallableAttribute`, so that C# code could reasonably declare methods visible to Java, along with the introduction of, and support for, a new `Java.Interop.JavaCallableConstructorAttribute` type. This allows straightforward usage: [JniTypeSignature ("example/ManagedType")] // for a nice Java name! class ManagedType : Java.Lang.Object { int value; [JavaCallableConstructor(SuperConstructorExpression="")] public ManagedType (int value) { this.value = value; } [JavaCallable ("getString")] public Java.Lang.String GetString () { return new Java.Lang.String ($"Hello from C#, via Java.Interop! Value={value}"); } } Run this through `jcw-gen` and `jnimarshalmethod-gen`, run the app, and nothing worked (?!), because not all pieces were in agreement. Java `native` method registration is One Of Those Things™ that involves lots of moving pieces: * `generator` emits bindings for Java types, which includes Java method names, signatures, and (on .NET Android) the "connector method" to use: [Register ("toString", "()Ljava/lang/String;", "GetToStringHandler")] // .NET Android [JniMethodSignature ("toString", "()Ljava/lang/String;")] // Java.Base public override unsafe string? ToString () {…} * `jcw-gen` uses `generator` output, *prefixing* Java method names with `n_` for `native` method declarations, along with a method wrapper [^1] public String toString() {return n_toString();} private native String n_toString(); * `jnimarshalmethod-gen` emits marshal methods for Java.Base, and needs to register the `native` methods declared by `jcw-gen`. `jnimarshalmethod-gen` and `jcw-gen` need to be consistent with each other. * `MarshalMemberbuilder.CreateMarshalToManagedMethodRegistration()` creates a `JniNativeMethodRegistration` instance which contains the name of the Java `native` method to register, and was using a name inconsistent with `jcw-gen`. Turns Out, `jcw-gen`, `jnimarshalmethod-gen`, and `MarshalMemberBuilder` were *not* consistent. The only "real" `jnimarshalmethod-gen` usage (16cd04f) is with the `Java.Interop.Export-Tests` unit test assembly, which *did not use* `jcw-gen`; it contained only hand-written Java code. Consequently, *none* of the Java `native` methods declared within it had an `n_` prefix, and since this worked with `jnimarshalmethod-gen`, this means that `jnimarshalmethod-gen` registration logic likewise didn't use `n_` prefixed method names. The result is that in the NativeAOT app, it would attempt to register the `native` Java method `ManagedType.getString()`, while what `jcw-gen` declared was `ManagedType.n_getString()`! Java promptly threw an exception, and the app crashed. Update `Java.Interop.Export-Tests` so that all the methods used with `MarshalMemberBuilder` are declared with `n_` prefixes, and add a `Java.Lang.Object` subclass example to the unit tests: Update `tests/Java.Interop.Tools.JavaCallableWrappers-Tests` to add a test for `.CodeGenerationTarget==JavaInterop1`. Add `$(NoWarn)` to `Java.Interop.Tools.JavaCallableWrappers-Tests.csproj` in order to "work around" warnings-as-errors: …/src/Java.Interop.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs(19,63): error CA1019: Remove the property setter from Name or reduce its accessibility because it corresponds to positional argument name …/src/Java.Interop.NamingCustomAttributes/Android.Runtime/RegisterAttribute.cs(53,4): error CA1019: Remove the property setter from Name or reduce its accessibility because it corresponds to positional argument name …/src/Java.Interop.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs(12,16): error CA1813: Avoid unsealed attributes … These are "weird"; the warnings/errors appear to come in because `Java.Interop.Tools.JavaCallableWrappers-Tests.csproj` now includes: <Compile Include="..\..\src\Java.Interop\Java.Interop\JniTypeSignatureAttribute.cs" /> which appears to pull in `src/Java.Interop/.editorconfig`, which makes CA1019 and CA1813 errors. (I do not understand what is happening.) Update `jnimarshalmethod-gen` so that the Java `native` methods it registers have an `n_` prefix. Refactor `ExpressionAssemblyBuilder.CreateRegistrationMethod()` to `ExpressionAssemblyBuilder.AddRegistrationMethod()`, so that the `EmitConsoleWriteLine()` invocation can provide the *full* type name of the `__RegisterNativeMembers()` method, which helps when there is more than one such method running around… Update `ExpressionAssemblyBuilder` so that the delegate types it creates for marshal method registration all have `[UnmanagedFunctionPointer(CallingConvention.Winapi)]`. (This isn't needed *here*, but is needed in the context of NativeAOT, as NativeAOT will only emit "marshal stubs" for delegate types which have `[UnmanagedFunctionPointer]`.) Unfortunately, adding `[UnmanagedFunctionPointer]` broke things: error JM4006: jnimarshalmethod-gen: Unable to process assembly '…/Hello-NativeAOTFromJNI.dll' Failed to resolve System.Runtime.InteropServices.CallingConvention Mono.Cecil.ResolutionException: Failed to resolve System.Runtime.InteropServices.CallingConvention at Mono.Cecil.Mixin.CheckedResolve(TypeReference self) at Mono.Cecil.SignatureWriter.WriteCustomAttributeEnumValue(TypeReference enum_type, Object value) … The problem is that `CallingConvention` was resolved from `System.Private.CoreLib`, and when we removed that assembly reference, the `CallingConvention` couldn't be resolved at all. We could "fix" this by explicitly adding a reference to `System.Runtime.InteropServices.dll`, but how many more such corner cases exist? The current approach is not viable. Remove the code from 16cd04f which attempts to remove `System.Private.CoreLib`. So long as `ExpressionAssemblyBuilder` output is *only* used in "completed" apps (not distributed in NuGet packages or some "intermediate" form), referencing `System.Private.CoreLib` is "fine". Update `jnimarshalmethod-gen` assembly location probing: in #1153, it was attempting to resolve the *full assembly name* of `Java.Base`, as `Java.Base, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null`, causing it to attempt to load the file `Java.Base, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null.dll`, which doesn't exist. Use `AssemblyName` to parse the string and extract out the assembly name, so that `Java.Base.dll` is probed for and found. Update `JreTypeManager` to *also* register the marshal methods generated by `Runtime.MarshalMemberBuilder.GetExportedMemberRegistrations()`. With all that, the updated `Java.Interop.Export-Tests` test now work both before and after `jnimarshalmethod-gen` is run: % dotnet test --logger "console;verbosity=detailed" bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll && dotnet bin/Debug-net7.0/jnimarshalmethod-gen.dll bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll && dotnet test --logger "console;verbosity=detailed" bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll … TODO: * `generator --codegen-target=JavaInterop1` should emit JNI method signature information for constructors! This would likely remove the need for `[JavaCallableConstructor(SuperConstructorExpression="")`. * dotnet/java-interop#1159 [0]: https://learn.microsoft.com/dotnet/core/deploying/native-aot [^0]: In a .NET Android environment, marshal methods are part of `generator` output, so things would be more straightforward there, though all the `_JniMarshal_*` types that are declared would also need to have `[UnmanagedFunctionPointer(CallingConvention.Winapi)]`… [^1]: Why not just declare `toString()` as `native`? Why have the separate `n_`-prefixed version? To make the `#if MONODROID_TIMING` block more consistent within `JavaCallableWrapperGenerator.GenerateMethod()`. It's likely "too late" to *easily* change this now.
Context: e75741e Context: https://xamarin.github.io/bugzilla-archives/23/2367/bug.html Context: https://github.com/xamarin/monodroid/commit/6679dfc62eae462b5acc9deb095d0fa41786f80b Constructors, how do they work? Commit e75741e goes into some details about the interaction between Java constructors and managed-side constructors when the Java constructor is invoked first. What happens when the managed-side constructor is invoked first? class JavaInteropExample : Java.Lang.Object { [JavaCallableConstructor(SuperConstructorExpression="")] public JavaInteropExample (int a, int b) {} } *Before* that code runs (ideally), `jcw-gen` (or equivalent) will run, creating a Java Callable Wrapper for `JavaInteropExample`, and that Java Callable Wrapper (JCW) will contain a constructor: // JCW /* partial */ class JavaInteropExample extends java.lang.Object { public JavaInteropExample(int p0, int p1) { super (); if (getClass () == JavaInteropExample.class) { ManagedPeer.construct (…); } } } Then someone tries: // C# var o = new JavaInteropExample(42); What happens is: 1. `JavaInteropExample(int, int)` constructor begins execution, *immediately* executes (implicit) base constructor invocation `: base()`. 2. `Java.Lang.Object` default constructor executes, which invokes `JavaObject(ref JniObjectReference, JniObjectReferenceOptions)` constructor, which is a no-op as the `JniObjectReference` is invalid. 3. `Java.Lang.Object` default constructor continues, hitting: var peer = JniPeerMembers.InstanceMethods.StartCreateInstance ("()V", GetType (), null); which looks up the Java peer, and invokes the default constructor on the Java peer. 4. `JavaObject.PeerReference` (eventually) is `peer.NewGlobalRef()`, and the `JavaInteropExample` constructor can *now* begin executing. If the JCW doesn't contain a default constructor, then things fail: Error Message: Java.Interop.JavaException : Lnet/dot/jni/test/JavaCallableExample;.<init>()V Stack Trace: at Java.Interop.JniEnvironment.InstanceMethods.GetMethodID(JniObjectReference type, String name, String signature) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/obj/Debug/net7.0/JniEnvironment.g.cs:line 19947 at Java.Interop.JniType.GetConstructor(String signature) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniType.cs:line 182 at Java.Interop.JniPeerMembers.JniInstanceMethods.GetConstructor(String signature) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs:line 63 at Java.Interop.JniPeerMembers.JniInstanceMethods.FinishCreateInstance(String constructorSignature, IJavaPeerable self, JniArgumentValue* parameters) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs:line 173 at Java.Lang.Object..ctor() in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Base/obj/Debug-net7.0/mcw/Java.Lang.Object.cs:line 33 at Java.InteropTests.JavaCallableExample..ctor(Int32 a) in /Users/jon/Developer/src/xamarin/java.interop/tests/Java.Interop.Export-Tests/Java.Interop/JavaCallableExample.cs:line 11 at Java.InteropTests.JavaCallableExampleTest.ManagedCtorInvokesJavaDefaultCtor() in /Users/jon/Developer/src/xamarin/java.interop/tests/Java.Interop.Export-Tests/Java.Interop/JavaCallableExampleTests.cs:line 22 at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr) --- End of managed Java.Interop.JavaException stack trace --- java.lang.NoSuchMethodError: Lnet/dot/jni/test/JavaCallableExample;.<init>()V But why would the JCW for `JavaInteropExample` contain a default constructor at all? In .NET Android, bound constructors have `[Register]`, and `jcw-gen` will emit constructors based on "visible" `[Register]`ed constructors. This ensures that we get *at least one* constructor that exists in Java, which the C# derived types will need to invoke. `generator --codegen-target=JavaInterop1`-style bindings didn't previously emit anything for bound constructors, preventing `jcw-gen` from performing this same logic. Consequently, the JCW for `JavaInteropExample` *didn't* have a default constructor. Add a new `Java.Interop.JniConstructorSignatureAttribute` type, and update `generator` to emit this attribute on bound constructors. Update `jcw-gen` to support `JniConstructorSignatureAttribute`. This adds a default constructor to `JavaInteropExample`: // JCW /* partial */ class JavaInteropExample extends java.lang.Object { public JavaInteropExample() { super (); if (getClass () == JavaInteropExample.class) { ManagedPeer.construct (…); } } public JavaInteropExample(int p0, int p1) { super (); if (getClass () == JavaInteropExample.class) { ManagedPeer.construct (…); } } } Note: while there is a public default constructor on the JCW, Java code cannot call it. If it attempts to do so, an exception will be thrown because the C#-side type doesn't have a default constructor.
Changes: dotnet/android-tools@8a971d9...8d38281 * dotnet/android-tools@8d38281: Update the maximum NDK version to 26 (dotnet/android-tools#219) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Fixes: dotnet/java-interop#1159 Context: c6faab7 `jnimarshalmethod-gen` is intended to generate marshal methods for any type that that: * Has `[JavaCallable]` (tested in `Java.Interop.Export-Tests.dll` and c6faab7), or * Overrides a `virtual` method which has `[JniMethodSignature]`, or * Implements an interface method which has `[JniMethodSignature]`. Thus, the intention was that it should generate marshal methods for `Java.Base-Tests`: % dotnet bin/Debug-net7.0/jnimarshalmethod-gen.dll -v bin/TestDebug-net7.0/Java.Base-Tests.dll Unable to read assembly 'bin/TestDebug-net7.0/Java.Base-Tests.dll' with symbols. Retrying to load it without them. Preparing marshal method assembly 'Java.Base-Tests-JniMarshalMethods' Processing Java.BaseTests.JavaInvoker type Processing Java.BaseTests.MyRunnable type Processing Java.BaseTests.MyIntConsumer type Marshal method assembly 'Java.Base-Tests-JniMarshalMethods' created Notably missing? No messages stating: Adding marshal method for … Also missing? `ikdasm bin/TestDebug-net7.0/Java.Base-Tests.dll` showed that there were no `__RegisterNativeMembers()` methods emitted. The `jnimarshalmethod-gen` invocation was a no-op! The problems were twofold: 1. It was only looking for methods with `Android.Runtime.RegisterAttribute`. This was useful for Xamarin.Android (when we were trying to make it work), but doesn't work with Java.Base. We need to *also* look for `Java.Interop.JniMethodSignature`. Relatedly, the attempt to use `registerAttribute.Constructor.Parameters` to determine parameter names didn't work; the parameter name was always `""`. 2. A'la c6faab7, we need to ensure that the Java `native` methods we register are consistent with `jcw-gen` output. Fix these two problems, which allows `jnimarshalmethod-gen` to now emit marshal methods for types within `Java.Base-Tests.dll`. Additionally, rework the `jnimarshalmethod-gen -f` logic to *remove* the existing `__<$>_jni_marshal_methods` nested type when present.
The eventual plan is to move the xamarin/Java.Interop repo to dotnet/jni (timeline: unspecified), and existing Java code within dotnet/runtime (for use with .NET Android) already uses a `net.dot` package-name prefix. As "xamarin" is increasingly "persona-non-grata", *and* these types aren't actually used publicly -- .NET Android née Xamarin.Android has it's own set of `mono.android` types -- we can safely rename the `com.xamarin.java_interop` and related packages to instead use a `net.dot.jni` prefix.
…1472) Setting max-ai-credits: -1 disables token steering, which the AWF firewall needs to inject Copilot provider auth. The api-proxy returned HTTP 403 on /models and Copilot CLI bailed with 'Authentication failed with provider'. Use 100M (effectively unlimited) instead so per-run cost is uncapped while token steering stays on. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
) PR #1469 added `copilot-requests: write` to the java-interop-reviewer workflow permissions block to opt into the new feature where gh-aw uses the built-in GITHUB_TOKEN for Copilot CLI inference, with AI credits billed to the org. That feature requires the "Allow use of Copilot CLI billed to the organization" Copilot policy to be enabled at the org level. The dotnet org has not enabled that policy yet, so the workflow now fails with HTTP 403 from api.githubcopilot.com/models. See failing run: https://github.com/dotnet/java-interop/actions/runs/27628125187/job/81695629467 Revert just the opt-in line in java-interop-reviewer.md and recompile the lock file. After this merges, the COPILOT_GITHUB_TOKEN secret needs to be re-added to the copilot-pr-reviewer environment so the PAT-based flow works again. Also revert max-ai-credits from 100M back to -1 (truly unlimited). PR #1472 capped it at 100M based on a wrong theory that -1 was causing the auth 403; that 403 reproduced under 100M too, so the cap wasn't the issue and the user prefers -1. The gh-aw CLI v0.79.8 bump and the agentics-maintenance.yml updates from PR #1469 are intentionally left in place. See: https://github.blog/changelog/2026-06-11-agentic-workflows-no-longer-need-a-personal-access-token/ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
) ## Description The binding generator emits a `Java.Interop.__TypeRegistrations` class into every binding assembly (including `Mono.Android.dll` itself), but the runtime code that consumed it was removed in **[#9471](#9471. The generated class is now pure dead code — it is never invoked and the dictionary it populates is never read. This stops generating it. ### Background `__TypeRegistrations.RegisterPackages ()` called `Java.Interop.TypeManager.RegisterPackages (string[], Converter<string, Type?>[])`, which populated a `packageLookup` dictionary. The Java→managed type resolver had a `TypeRegistrationFallback (class_name)` path that lazily invoked `__TypeRegistrations.RegisterPackages ()` and then read `packageLookup`. That fallback was put behind an `AppContext` switch in [#5629](#5629), further trimmed in [#8572](#8572), and **fully removed in [#9471](#9471. Today the resolver goes exclusively through the native typemap / trimmable type map, so the generated class and its `RegisterPackages`/`Lookup` helpers are unreachable. ### Changes - Remove `ClassGen.GenerateTypeRegistrations ()` and its call site in `CodeGenerator.Run ()`. - Delete the `Java.Interop.__TypeRegistrations.cs` baselines from the generator-Tests `expected.xaji` fixtures. - Remove the now-stale `<Compile Include="…Java.Interop.__TypeRegistrations.cs" />` entry from each generated `Mono.Android.projitems` baseline (the `.projitems` is generated from the set of emitted files, so it no longer lists the removed file). ### Testing `generator-Tests`: **455 passed, 0 failed** locally (rebased onto current `main`). ### Coordination This is the **generator-side** half of **#11663**. The **consumer-side** half — removing the now-dead `packageLookup` runtime plumbing in `Mono.Android` — is **#11667**. The two are intentionally **decoupled** (no submodule bump pinning them together): each is independently mergeable, and this change reaches `Mono.Android` through the normal `external/Java.Interop` submodule bump once it merges into `main`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Phase 2 of #1431. Builds on #1432 (sibling-collision dedup) by detecting Kotlin `@JvmInline value class` types in `class-parse`, surfacing them in `api.xml`, and projecting them into generator output as `readonly struct` wrappers — while JNI marshaling stays on the underlying primitive. ## What this does Given Kotlin source like: ```kotlin @JvmInline value class MyColor(val value: ULong) @JvmInline value class MyAlpha(val value: ULong) @JvmInline value class MyDp(val value: Float) object Widgets { fun tint(c: MyColor) { ... } fun tint(a: MyAlpha) { ... } fun tint(d: MyDp) { ... } fun pad(d: MyDp): MyDp { ... } fun pad(x: MyDp, y: MyDp): MyDp { ... } } ``` The generator now emits: ```csharp public readonly partial struct MyColor : System.IEquatable<MyColor> { public readonly long Value; public MyColor (long value) { Value = value; } public static implicit operator long (MyColor value) => value.Value; public static implicit operator MyColor (long value) => new MyColor (value); // == / != / Equals / GetHashCode / ToString } // ...same shape for MyAlpha (long-backed) and MyDp (float-backed). public static class Widgets { public static void Tint (MyColor c) { ... } // was: void Tint_Rn_QMJI (long c) public static void Tint (MyAlpha a) { ... } // was: void Tint_uzYZ1wI (long a) public static void Tint (MyDp d) { ... } // was: void Tint_L3D9Hvg (long d) public static MyDp Pad (MyDp d) { ... } public static MyDp Pad (MyDp x, MyDp y) { ... } } ``` The bodies of `Tint`/`Pad` still pass the JVM-erased primitive (`long`/`float`) across JNI — the implicit conversion operators on the struct make that compile without any thunk changes. ## Bytecode layer - New `ClassFile.KotlinInlineClassUnderlyingJniType`, `MethodInfo.KotlinInlineClassReturnJniType`, `MethodInfo.KotlinName`, `ParameterInfo.KotlinInlineClassJniType`. - `KotlinFixups.Fixup` does a pre-pass `DetectInlineClasses` that recognizes `@JvmInline` + `KotlinClassFlags.IsInlineClass` + a **single non-synthetic instance field with a primitive descriptor**, then stamps each method's parameter/return JNI types when the Kotlin source-level type was an inline class **and** the JVM-erased descriptor matches the underlying primitive (so boxed/nullable positions are correctly skipped). - Recovers the unmangled Kotlin source name from metadata (`tint` instead of `tint-Rn_QMJI`) and surfaces it via `MethodInfo.KotlinName`. - `XmlClassDeclarationBuilder` emits `kotlin-inline-class`, `kotlin-inline-class-underlying-jni-type`, `kotlin-inline-class-jni-type` (parameter), `kotlin-inline-class-return-jni-type` (method), and `managedName` (unmangled Kotlin name) attributes. ## Generator layer - `ClassGen.IsKotlinInlineClass` / `ClassGen.KotlinInlineClassUnderlyingJniType`. - `Parameter.KotlinInlineClassJniType` / `Method.KotlinInlineClassReturnJniType`. - `XmlApiImporter` reads the new attributes; the existing `managedName` codepath delivers the unmangled C# binding name without affecting JNI invocation. - `Parameter.Validate` / `ReturnValue.Validate` apply the projection by looking up the wrapper `ClassGen` via `SymbolTable` and overriding `managed_type` so `Type`/`FullName` return the struct while `Symbol` (and therefore JNI marshaling) stays on the underlying primitive. - `MethodBase.Matches` also compares `Parameters[i].KotlinInlineClassJniType` so two inline classes that share the same JVM primitive (e.g. multiple `ULong`-backed inline classes on Compose APIs) survive Phase 1's `RemoveCollidingSiblings` as distinct overloads. - `Parameter.ShouldGenerateKeepAlive` returns `false` for projected inline-class params (avoids needless `GC.KeepAlive` on a value-type wrapping a primitive). - New `KotlinInlineClassStruct` `TypeWriter` emits the wrapper struct. - `JavaInteropCodeGenerator.WriteType` routes inline-class `ClassGen` instances to the new writer instead of `BoundClass`. - New `TypeNameUtilities.JniSignatureToJavaTypeName` helper (handles nested types via `$` → `.`). ## Tests - **Bytecode-Tests**: 82 passed (2 pre-existing skips). New `KotlinInlineClassCollisionTests` exercise the real Kotlin `.class` fixtures. - **generator-Tests**: 7 unit tests in `KotlinInlineClassTests` plus 1 end-to-end test in `KotlinInlineClassEndToEndTests` that drives real Kotlin `.class` files (compiled by `tests/Xamarin.Android.Tools.Bytecode-Tests/kotlin-gradle/`) through `KotlinFixups` → `XmlClassDeclarationBuilder` → `XmlApiImporter` → `Java.Interop.Tools.Generator.Transformation.KotlinFixups` → `Validate` → `JavaInteropCodeGenerator`. The end-to-end test mirrors the real `CodeGenerator.Generate` pipeline and would have caught the `RemoveCollidingSiblings` regression that motivated the `MethodBase.Matches` fix. - 23 pre-existing `generator-Tests` failures are unrelated environment issues (Roslyn / FileStream); confirmed unchanged. ## Build-system The Kotlin/Gradle fixture target (`BuildKotlinGradleProject`) was extracted into a shared `tests/Xamarin.Android.Tools.Bytecode-Tests/kotlin-gradle.targets` file imported by both `Bytecode-Tests.csproj` and `generator-Tests.csproj`, so the `.class` files are produced regardless of which test project the build hits first. ## Out of scope / known limitations - **Boxed inline-class positions** (nullable like `MyColor?`, generics like `List<MyColor>`, supertype implementations) still emit `Lcom/example/MyColor;` references that no longer have a peer class binding. These were rare on the targeted Compose surface; users can unblock them via Metadata.xml (`<remove-node>`) until a follow-up. - **Reference-backed inline classes** (e.g. `value class Tag(val raw: String)`) are detected as inline classes but the projection path requires a primitive backing field; non-primitive-backed inline classes are skipped and fall through to the legacy peer-class path. - **Generic inline classes** (`value class Wrap<T>(val v: T)`). - **Validation against the Compose `material3-android` AAR** is not part of this PR. Closes part of #1431. Phase 1 (sibling-collision dedup) was #1432. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… "trimmable type map" (#1454) Follow-up to dotnet/java-interop#1441. Prerequisite for #11617. This keeps the Java.Interop changes focused on the small base hook dotnet/android needs for the trimmable type-map integration. The reflection value manager continues to use value marshalers internally, while Android's generated/trimmable value manager can provide the only production `JavaObjectArray<T>` element-assignment object-reference path without implementing `GetValueMarshaler*()`. ## Changes - Add minimal `JniValueManager` object-reference API for `JavaObjectArray<T>.SetElementAt()`: - `CreateLocalObjectReferenceArgument(Type type, object? value)` returns an owned local `JniObjectReference` for element assignment. Callers must dispose the returned reference. - Make the matching core method abstract so non-reflection value managers can implement this path directly. - Keep value marshalers as a `ReflectionJniValueManager` implementation detail: reflection creates marshaler state, copies out an independent local reference, then destroys the state immediately. - Update `JavaObjectArray<T>` to call the value manager directly instead of calling `GetValueMarshaler<T>()` in production paths. - Simplify `JavaObjectArray<T>.Clear()` to set array slots to Java null directly; it no longer needs value-manager or value-marshaler state. - Remove the earlier exposed proxy/peerable marshaler accessors, broad/generic state overloads, default-value state API, destroy-state API, and `ParameterAttributes synchronize` from this value-manager object-reference path. - Keep ManagedPeer-dependent tests categorized as unsupported for the Android trimmable configuration rather than carrying Android-specific Java fixture workarounds in this PR. - Include the small type-manager/test cleanups needed by the dotnet/android integration branch. ## Non-goals - This PR does not make value marshalers public trimmable API. - This PR does not require trimmable Android value managers to implement or use `GetValueMarshaler*()`. - This PR does not remove or replace ManagedPeer-dependent Java.Interop test fixtures. ## Validation - `dotnet build external/Java.Interop/src/Java.Interop/Java.Interop.csproj -p:Configuration=Debug -m:1 -nodeReuse:false --no-restore -v:minimal` - From the dotnet/android integration branch: - `dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj -v minimal --no-restore` (`562` passed) - `dotnet build src/Mono.Android/Mono.Android.csproj -p:Configuration=Debug -p:AndroidSdkDirectory=/Users/simonrozsival/android-toolchain/sdk -m:1 -nodeReuse:false --no-restore -v:minimal` compiled `Mono.Android.Runtime.dll`; the remaining local failure is Android SDK provisioning (`extras/android/m2repository.staging` and `docs.staging` missing), not C# or trim-analyzer errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes #1359 ## Summary This reduces generated JNI callback IL size by moving the repeated marshal-boundary wrapper code out of every generated callback and into generated helper overloads. Generated callbacks now have two pieces: - `n_*`: a small native-entry thunk that forwards JNI arguments to a generated safe invoker. - `__n_*`: the actual callback body that performs `GetObject`, argument conversion, user call/property access, cleanup, and return marshaling. `Java.Interop.JniMarshal` now provides shared `SafeInvokeAction` and `SafeInvokeFunc` overloads. These helpers centralize: - `JniEnvironment.BeginMarshalMethod(...)` - `try` / `catch (Exception)` / `finally` - `JniRuntime.OnUserUnhandledException(...)` - `JniEnvironment.EndMarshalMethod(...)` The helper shape is intentionally C#-only and uses managed function pointers with the function pointer as the final argument: ```csharp Java.Interop.JniMarshal.SafeInvokeAction (jnienv, native__this, arg0, arg1, &__n_Foo); Java.Interop.JniMarshal.SafeInvokeFunc (jnienv, native__this, arg0, &__n_Bar); ``` ## Design notes During prototyping I tested several alternatives: - explicit IL `tail.` thunks - C# forwarding patterns that might encourage tailcalls - NativeAOT output for C# forwarding patterns - function pointer first vs. function pointer last The useful findings were: - C# and NativeAOT do not reliably emit tailcalls for these forwarding patterns. - Explicit `tail.` can help low-arity compiled IL thunks, but has severe arity/ABI cliffs. On arm64, mixed signatures around 8+ args fell back to helper-based tailcalls and became ~4x slower than normal calls. - Function-pointer-last preserves JNI argument register/stack placement better than function-pointer-first. So this PR deliberately avoids explicit `tail.` and uses the safer C# helper shape with JNI args first and fnptr last. The safe invokers live in `Java.Interop.JniMarshal` rather than generated binding output, so each binding assembly only emits the small `n_*`/`__n_*` split and does not get its own copy of the helper methods. ## DebuggerDisableUserUnhandledExceptions placement This PR does not remove `[global::System.Diagnostics.DebuggerDisableUserUnhandledExceptions]` from the marshal exception boundary. It moves the attribute to the shared `Java.Interop.JniMarshal.SafeInvokeAction` / `SafeInvokeFunc` helpers because those helpers now own the `try` / `catch (Exception)` block and call `OnUserUnhandledException(...)`. The generated `n_*` methods are now only forwarding thunks and no longer catch user exceptions. Keeping the attribute on every `n_*` thunk would be redundant for debugger behavior and would add back per-callback metadata/IL that this change is trying to remove. The attribute follows the catch block. ## Mono.Android size measurements Measured by building Mono.Android from a dotnet/android worktree with this Java.Interop generator patch applied. The shared-helper version has no generated `__JniMarshalMethodHelper` copies (`0` matches in generated mcw output). Generated callbacks call `Java.Interop.JniMarshal.SafeInvoke*` instead (`29,967` call sites in Mono.Android generated output). ### Release | Artifact | Baseline | Patched | Delta | |---|---:|---:|---:| | Runtime `Mono.Android.dll` | 42,798,592 | 42,121,728 | **-676,864 bytes** | | Ref `Mono.Android.dll` | 19,295,744 | 19,339,776 | +44,032 bytes | | `Mono.Android.pdb` | 32,553,308 | 30,690,252 | **-1,863,056 bytes** | | Generated mcw `.cs` total | 29,234,484 | 28,508,893 | **-725,591 bytes** | ### Debug | Artifact | Baseline | Patched | Delta | |---|---:|---:|---:| | Runtime `Mono.Android.dll` | 46,168,064 | 45,507,584 | **-660,480 bytes** | | Ref `Mono.Android.dll` | 19,294,720 | 19,339,264 | +44,544 bytes | | `Mono.Android.pdb` | 38,057,736 | 35,815,552 | **-2,242,184 bytes** | | Generated mcw `.cs` total | 29,234,484 | 28,508,893 | **-725,591 bytes** | Compared with the earlier generated-helper prototype, moving the helper into `Java.Interop.JniMarshal` saves an additional ~11.5 KiB in runtime `Mono.Android.dll`, ~8.5 KiB in ref `Mono.Android.dll`, and ~6.8 KiB of generated mcw source. Measurement notes are saved locally at: `~/.copilot/session-state/fd1f37cb-e759-40da-8b40-c668a63336f6/files/android-issue-1359-shared-helper-measure-summary.txt` ## Validation - `dotnet build tools/generator/generator.csproj -v:minimal` - `dotnet test tests/generator-Tests/generator-Tests.csproj -v:minimal` - 455 passed, 0 failed Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add explicit github-token entries under tools.github and safe-outputs in
.github/workflows/java-interop-reviewer.md so the compiled lock file no
longer references the gh-aw fallback secret names GH_AW_GITHUB_TOKEN and
GH_AW_GITHUB_MCP_SERVER_TOKEN.
These names appear in the lock file purely as fallback chain entries that
are never set in this repo. A secret-audit report flags by name reference
in the YAML, so suppressing them satisfies the audit with no behavior change.
Before/after of the 'Secrets used:' header in the lock file:
Before: COPILOT_GITHUB_TOKEN, GH_AW_GITHUB_MCP_SERVER_TOKEN,
GH_AW_GITHUB_TOKEN, GITHUB_TOKEN
After: COPILOT_GITHUB_TOKEN, GITHUB_TOKEN
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bumps [external/xamarin-android-tools](https://github.com/xamarin/xamarin-android-tools) from `5165523` to `132f790`. - [Commits](dotnet/android-tools@5165523...132f790) --- updated-dependencies: - dependency-name: external/xamarin-android-tools dependency-version: 132f790353413dcaef231e720e255364a310b3bd dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Context from dotnet/android PR #11617: CoreCLR trimmable typemap runs fail tests that depend on unsupported reflection/value-marshaler behavior. This marks the following tests as `TrimmableTypeMapUnsupported`: - `JniValueMarshalerContractTests<T>`: these directly test the reflection-based value marshaler APIs. The trimmable typemap runtime intentionally throws if `GetValueMarshalerCore()` is called. - `JniPeerMembersTests.VirtualInvokeOnBaseInvokesMostDerivedJavaMethod`: this still unexpectedly reaches `GetValueMarshalerCore<T>()` through the test helper constructor path; it should be fixed separately. - `JavaObjectArray_object_ContractTest`: these inherited collection/list contract tests depend on plain managed `object` values round-tripping through `JavaProxyObject`. The trimmable value manager currently falls back to `JavaConvert.ToLocalJniHandle(value)`, which wraps plain objects as `Android.Runtime.JavaObject` (`mono/android/runtime/JavaObject`) instead of `Java.Interop.JavaProxyObject` (`net/dot/jni/internal/JavaProxyObject`). Tracking issue for the JavaProxyObject/trimmable typemap support gap: #11703. Validation: - `dotnet build external/Java.Interop/tests/Java.Interop-Tests/Java.Interop-Tests.csproj --no-restore --verbosity:minimal` - Local dotnet/android CoreCLR trimmable device run with `-p:PublishReadyToRun=false`: after these exclusions, the run drops to 2 failures (`TrimmableTypeMapTypeManagerTests.JavaProxyObject_ObjectMethodsUseJavaIdentitySemantics` in dotnet/android and `JniTypeManagerTests.GetType`, which is unrelated to JavaProxyObject object-array contracts). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…heDirectory (#1480) Adds a path-staying-under-CacheDirectory assertion to `CachedMavenRepository`. Exposes a new public API, `GetArtifactFilePath (Artifact, string)`, that returns the on-disk path where an artifact + filename would be cached and throws `InvalidOperationException` if the resolved path would not be under `CacheDirectory`. `TryGetFile`, `TryGetFilePath`, and `GetFilePathAsync` all route through this single method so there is exactly one place that knows the cache layout. The new public API lets callers (such as dotnet/android's `MavenExtensions.DownloadPayload`) stop reconstructing the cache path themselves with their own `Path.Combine` logic and get the assertion for free. Defense-in-depth, hardening. Not security enforcement. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Context: https://github.com/dotnet/android (the `<AndroidMavenLibrary>` MSBuild item relies on `Artifact`/`Artifact.TryParse` to validate Maven coordinates supplied by users, but today the constructor blindly assigns its fields and `TryParse` only checks for three colon-separated parts. Empty strings, whitespace, and characters that are illegal per the Maven coordinate spec all slip through. Add structural validation to `Artifact`: * `groupId` and `artifactId` must match `[A-Za-z0-9_\-.]+` per https://maven.apache.org/pom.html#Maven_Coordinates. * `version` rejects whitespace, `:`, and path separators (`/`, `\`). Maven versions are otherwise permissive, so no further parsing. * The constructor throws `ArgumentNullException` / `ArgumentException` with the offending parameter and value. * `TryParse` returns `false` for any invalid input (and requires all three parts to be non-empty) without throwing. * `Parse` continues to throw `ArgumentException` with the existing message format. The constructor still allows an empty `version` so that existing internal callers - `Dependency.ToArtifact ()` and `Project.TryGetParentPomArtifact ()` - can keep producing partial coordinates when a POM omits `<version>` and inherits it from a parent. `TryParse`/`Parse` (the user-input path) still require a non-empty version. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
5617e5e to
258d666
Compare
Brings the full history of https://github.com/dotnet/java-interop into dotnet/android under external/Java.Interop/, replacing the previous git submodule. All original commits are preserved with their original author, committer, author-date, committer-date, and message. Paths were rewritten using `git filter-repo --to-subdirectory-filter external/Java.Interop`. History is rewound to the SHA previously pinned in the submodule (dotnet/java-interop@70493645) -- the rewritten equivalent 748e962 -- so this PR mirrors exactly what was in-tree before, with no behavioral change. Newer java-interop commits can be brought in via a follow-up.
a2409b0 to
8790c0e
Compare
220f39b to
71db2de
Compare
Output Binary ComparisonCompared the artifacts from this PR's dotnet/android binaries — effectively identical
Per-package breakdown of Java.Interop lanes vs upstream
|
Adds a shared 'Java.Interop Tests' stage template at build-tools/automation/yaml-templates/stage-java-interop-tests.yaml that runs the two jobs from the upstream java-interop pipeline (Windows - .NET, Mac - .NET) using the in-tree external/Java.Interop/build-tools/automation/templates/ files. The stage is wired into both the official pipeline (azure-pipelines.yaml) and the public PR validation pipeline (azure-pipelines-public.yaml) so they stay in lockstep with zero duplication. A follow-up PR will reorganize external/Java.Interop and dedupe its nested external/* submodules against dotnet/android's own external/.
71db2de to
516f111
Compare
dotnet/android does not normally allow merge commits. The maintainer landing
this PR needs to JIT-elevate to temporarily enable the merge-commit
option, land the PR, and then revert the setting. If this PR is squashed or
rebased, the full per-commit authorship history from dotnet/java-interop is
lost.
What this does
Replaces the
external/Java.Interopgit submodule with an in-tree copy ofthe entire
dotnet/java-interophistory, rewritten so every commit's treelives under
external/Java.Interop/.dotnet/java-interop@mainpreserved verbatim on every commit — no
Co-authored-bytrailers addedinherent to subdirectory filtering
How it was done
git clone https://github.com/dotnet/java-interop ji-rewrite cd ji-rewrite git filter-repo --to-subdirectory-filter external/Java.Interopthen in this branch:
Commits on this branch
CI changes
Adds a shared stage template
build-tools/automation/yaml-templates/stage-java-interop-tests.yamlthatmirrors the two jobs from upstream
external/Java.Interop/build-tools/automation/azure-pipelines.yaml:windows-2025, native dotnettests,
nativeAotRid: win-x64)macOS-15, native tests,nativeAotRid: osx-x64)The stage is wired into both pipelines (zero duplication):
build-tools/automation/azure-pipelines.yaml(official / 1ES)build-tools/automation/azure-pipelines-public.yaml(public PR validation)Follow-up PR (not this one)
external/Java.Interop/external/Java.Interop/external/*submodules againstdotnet/android's own
external/*(e.g.xamarin-android-tools)Checklist for the merger