Skip to content

Merge dotnet/java-interop into external/Java.Interop with full history#11744

Draft
jonathanpeppers wants to merge 1507 commits into
mainfrom
jonathanpeppers-java-interop-migration-plan
Draft

Merge dotnet/java-interop into external/Java.Interop with full history#11744
jonathanpeppers wants to merge 1507 commits into
mainfrom
jonathanpeppers-java-interop-migration-plan

Conversation

@jonathanpeppers

Copy link
Copy Markdown
Member

⚠️ MUST be merged with "Create a merge commit" — not squash, not rebase

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.Interop git submodule with an in-tree copy of
the entire dotnet/java-interop history, rewritten so every commit's tree
lives under external/Java.Interop/.

  • 1506 commits brought in from dotnet/java-interop@main
  • Original author, committer, author-date, committer-date, and message
    preserved verbatim on every commit — no Co-authored-by trailers added
  • Commit SHAs change because trees change (paths rewritten); this is
    inherent 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.Interop

then in this branch:

git submodule deinit -f external/Java.Interop
git rm -f external/Java.Interop                                    # commit 1
git merge --allow-unrelated-histories --no-ff ji-rewrite/main      # merge commit
# add CI plumbing                                                   # commit 2

Commits on this branch

* Add Java.Interop CI stage and merge follow-ups
*   Merge dotnet/java-interop history under external/Java.Interop
|\
| * (1506 commits, original authors + dates: 2014-01-02 .. 2026-06-24)
* Remove external/Java.Interop submodule in preparation for in-tree merge

CI changes

Adds a shared stage template
build-tools/automation/yaml-templates/stage-java-interop-tests.yaml that
mirrors the two jobs from upstream
external/Java.Interop/build-tools/automation/azure-pipelines.yaml:

  • Java.Interop Tests > Windows - .NET (windows-2025, native dotnet
    tests, nativeAotRid: win-x64)
  • Java.Interop Tests > Mac - .NET (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)

  • Reorganize / consolidate code under external/Java.Interop/
  • Dedupe the nested external/Java.Interop/external/* submodules against
    dotnet/android's own external/* (e.g. xamarin-android-tools)
  • Delete code that's superseded by what already lives in dotnet/android

Checklist for the merger

  • JIT-elevate to enable "Create a merge commit" on dotnet/android
  • Click Create a merge commit (NOT squash, NOT rebase)
  • Revert the merge-commit setting after the PR lands
  • Coordinate archiving / read-only of dotnet/java-interop separately

jonpryor and others added 30 commits June 2, 2023 16:45
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.
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>&lt;pre&gt;&lt;p&gt;some content</c>&lt;/pre&gt;&lt;/p&gt;` 🤷‍♂️
  * `<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.
jonathanpeppers and others added 12 commits June 16, 2026 10:18
…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>
@jonathanpeppers jonathanpeppers added the use-merge-commit Normally we squash-and-merge PRs. Use this label so we instead use a merge commit. label Jun 25, 2026
@jonathanpeppers jonathanpeppers force-pushed the jonathanpeppers-java-interop-migration-plan branch 2 times, most recently from 5617e5e to 258d666 Compare June 25, 2026 21:34
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.
@jonathanpeppers jonathanpeppers force-pushed the jonathanpeppers-java-interop-migration-plan branch 4 times, most recently from a2409b0 to 8790c0e Compare June 26, 2026 01:43
@jonathanpeppers jonathanpeppers force-pushed the jonathanpeppers-java-interop-migration-plan branch 6 times, most recently from 220f39b to 71db2de Compare June 27, 2026 01:55
@jonathanpeppers

Copy link
Copy Markdown
Member Author

Output Binary Comparison

Compared the artifacts from this PR's dotnet-android build (#1483569, ji-migration on top of merge-base c2f553d7) against a sibling PR's build (#1483574, PR #11761, branched from current main with no ji changes). Both built within minutes of each other on the same agents.

dotnet/android binaries — effectively identical

Artifact This PR (1483569) Baseline PR (1483574) Δ
nuget-unsigned 340,284,168 340,283,987 +181 B
nuget-unsigned-symbols 364,729,281 364,714,723 +14.5 KB
test-assemblies 576,572,509 576,549,009 +23.5 KB
windows-toolchain-pdb 484,696,718 484,696,718 0 — byte identical
nuget-linux-unsigned 126,878,161 126,878,133 +28 B
nuget-linux-unsigned-symbols 127,860,399 127,853,430 +7 KB
AndroidBuildToolsInventory 149 149 identical

Per-package breakdown of nuget-unsigned (18 packages on each side, all matching by name modulo PR-number tag) — largest delta is 68 bytes on the 145 MB Microsoft.Android.Sdk.Darwin nupkg. All within normal non-deterministic packaging variance (zip timestamps, PDB GUIDs).

Java.Interop lanes vs upstream dotnet.java-interop

Compared this PR's Java.Interop Tests Windows - .NET / Mac - .NET jobs (build 1483569) against upstream dotnet.java-interop build 1477466 at the exact same SHA (70493645c7d95648010a4cef948234a28744c03f — the SHA the submodule was pinned to).

  • Steps: Identical step lists in both jobs (Init → Checkout → Use .NET → Prepare Solution → Build Solution → ~22 Tests/Publish pairs → java-source-utils → NativeAOT publish/run → JUnit → fail-on-dirty-tree → fail-on-issue). Only differences are auto-injected pipeline policy tasks (CodeQL upstream, Component Detection here) — not template-driven.
  • TestRelease binaries (test outputs): same file counts (1271 / 1271), 3 MB size delta from non-deterministic packaging.
  • BuildRelease and BuildRelease-net10.0: byte-identical sizes (22 files / 0.4 MB and 5 files / 0.1 MB).
  • Release-net10.0 (utility tool binaries): missing 96 files (14.6 MB) here vs upstream — generator.dll, class-parse.dll, jcw-gen.dll, logcat-parse.dll, Java.Interop.Tools.Generator.dll, etc. They're still being built (verified in Build Solution log) but are redirected to bin/Release/lib/packs/Microsoft.Android.Sdk.Windows/37.0.0/tools/ because external/Java.Interop/Directory.Build.props imports external/Java.Interop.override.props, which sets UtilityOutputFullPathCoreApps to the dotnet/android SDK pack tree. This is intentional dotnet/android behavior (so ji tools land in the SDK pack); the ji standalone CI lane just inherits it. Tests still pass.

The Windows ji job's PublishPipelineArtifact@1 step was dropped — only logs/tests needed.

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/.
@jonathanpeppers jonathanpeppers force-pushed the jonathanpeppers-java-interop-migration-plan branch from 71db2de to 516f111 Compare June 27, 2026 01:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

use-merge-commit Normally we squash-and-merge PRs. Use this label so we instead use a merge commit.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants