Skip to content

Default Android type maps to trimmable - #12887

Merged
simonrozsival merged 8 commits into
mainfrom
simonrozsival-default-trimmable-typemap
Sep 30, 2026
Merged

simonrozsival merged 8 commits into
mainfrom
simonrozsival-default-trimmable-typemap

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Summary

Default AndroidTypeMapImplementation to trimmable for Android applications and libraries, and keep application-only typemap generation out of library builds.

This PR now includes three categories of work: the default switch, product fixes needed by the trimmable build path, and test/fixture migration. It is no longer just a property-default change.

Scope boundary: explicit llvm-ir selection is rejected with XA4267 before build, publish, or legacy generation. This is a behavior change beyond selecting a new default. The LLVM typemap generation and runtime implementation remain in the tree; their removal is left to follow-up PRs. Normal test matrices no longer select llvm-ir; intentional rejection tests remain.

Why the diff is larger than the default switch

At head 8e997831f3, the diff against the merge base is 45 files, +1,095 / −694:

Area Added Removed
Product code and MSBuild targets 344 17
Tests, fixtures, and APK baselines 714 671
Documentation 37 6

Almost all deletions are in tests and fixtures, not removal of the LLVM implementation. Selecting the trimmable typemap also selects a different MSBuild pipeline. Making that pipeline the default exposed generally required behavior that was previously wired only into the legacy path.

Product fixes included

  • Shared CoreCLR trimming behavior: wire linker descriptors, dependency dumps, shared post-trimming steps, and ProGuard configuration into the trimmable path. This addresses ignored preservation rules, missing configuration files, and missing keep-alive processing.
  • Multi-process applications: generate additional CoreCLR runtime providers such as MonoRuntimeProvider_1 for components running in separate Android processes.
  • Typemap packaging: correct assembly metadata to prevent double-counting typemap DLLs, and prepare resolved assemblies before adding pre-trim typemap inputs. The latter prevents Debug builds with trimming from packaging both pre-trim and linked copies under the same assembly name.
  • Fast Deployment: prefer the existing linked/R2R typemap image over a pre-trim copy targeting the same destination, avoiding startup MVID mismatches.
  • Deserialization: preserve the reflection-discovered callback that initializes a Java handle after deserialization.
  • Java-library ordering: categorize Android libraries before Java compilation so required classes are included in the application.

Opt-in legacy binding compatibility

The largest new mechanism is PreTrimmingFixLegacyBindings (123 lines), together with its MSBuild wiring and regression coverage.

When AndroidEnableLegacyCompatibilityAssemblyFixups=true, trimmed CoreCLR builds can repair older binding assemblies by injecting missing abstract-method implementations and rewriting legacy resource-field references to the current resource designer. These repairs run before ILLink, so trimming sees and preserves the dependencies introduced by the rewritten code.

Modified assemblies are written to project-local copies, not back into NuGet packages. The implementation preserves item metadata and symbols, avoids rewriting unchanged outputs, and handles missing-output regeneration for incremental builds.

This is a compatibility extension, not merely a default change: previously, this property did not enable these repairs for trimmed trimmable builds. It remains explicitly opt-in; the trimmable default does not enable legacy compatibility fixups globally. Legacy Xamarin.Forms, SkiaSharp, and binding fixtures opt in where they depend on this behavior.

Test and fixture changes

  • Remove legacy typemap cases and implementation-comparison branches while retaining trimmable CoreCLR and NativeAOT coverage. TrimmableTypeMapBuildTests.cs accounts for +98 / −325 of the diff.
  • Add compatibility-processing coverage for unchanged input assemblies, selected binding versions, output metadata, and incremental writes. Tasks/LinkerTests.cs accounts for +232 / −32.
  • Add default-selection and rejection coverage in InvalidConfigTests.cs (+75). XA4265 and XA4266 remain the upstream callback-format diagnostics; XA4267 is the unsupported-typemap diagnostic.
  • Update generated-Java paths, Java-name expectations, and incremental tests to check managed typemap artifacts rather than native typemap regeneration.
  • Preserve reflection-discovered Android test fixtures so trimming does not turn the JCW lanes into zero-test runs, and select the Android-safe shared Java fixtures.
  • Update obsolete Forms layout class names and regenerate APK-size baselines from measured Release outputs. Regression thresholds are unchanged.

There are also deliberate coverage removals: the LLVM-only post-link package-cache test is removed, and the fast-timing test is narrowed from concurrent buffer-growth coverage to timing-dump coverage. These are not simply assertion renames.

Local validation

  • Rebuilt the Release Android SDK and test assemblies.
  • 251 host cases passed across the affected typemap/build suites and refreshed APK-baseline runs; focused pre-trimming and incremental regressions also passed.
  • 49 device-integration cases passed, plus 773 runtime tests, 38 regular JCW tests, and 2 isolated JNI leak tests. Existing unsupported/explicit skips were retained.
  • The subsequent Debug-with-trimming packaging fix passed 20 targeted cases, including clean and incremental builds with R2R enabled and disabled, and typemap contents for both arm64 and x86_64.

Infrastructure-only CI failures were investigated separately rather than addressed by weakening assertions or changing product behavior.

Copilot AI lite review requested due to automatic review settings September 23, 2026 05:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved issues remain around library behavior, validation coverage, and generated resource documentation.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity · 1 Low severity

Open (2)
What changed in this PR

This PR makes trimmable the default Android type-map implementation and rejects application-level llvm-ir builds.

Changes:

  • Adds localized XA1051 validation and documentation.
  • Updates build ordering, defaults, and tests.
  • Removes obsolete llvm-ir-positive test cases.
File Summary
tests/​MSBuildDeviceIntegration/​Tests/​WebViewJavascriptInterfaceTests.cs Updates device test coverage.
tests/​MSBuildDeviceIntegration/​Tests/​InstallAndRunTests.cs Removes obsolete LLVM-IR test handling.
tests/​MSBuildDeviceIntegration/​Tests/​FastTimingTests.cs Updates device test coverage.
src/​Xamarin.Android.Build.Tasks/​Xamarin.Android.Common.targets Adds type-map validation.
src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.Android.Build.Tests/​TrimmableTypeMapBuildTests.cs Tests trimmable type-map behavior.
src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.Android.Build.Tests/​InvalidConfigTests.cs Tests invalid configuration diagnostics.
src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.Android.Build.Tests/​BuildWithLibraryTests.cs Updates library build tests.
src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.Android.Build.Tests/​BuildTest.cs Removes LLVM-IR-dependent coverage.
src/​Xamarin.Android.Build.Tasks/​Properties/​Resources.resx Adds and updates localized messages.
src/​Xamarin.Android.Build.Tasks/​Microsoft.Android.Sdk/​targets/​Microsoft.Android.Sdk.DefaultProperties.targets Defaults applications to trimmable.
src/​Xamarin.Android.Build.Tasks/​Microsoft.Android.Sdk/​targets/​Microsoft.Android.Sdk.BuildOrder.targets Runs validation during build ordering.
Documentation/​docs-mobile/​TOC.yml Updates documentation navigation.
Documentation/​docs-mobile/​messages/​xa1051.md Documents XA1051.
Documentation/​docs-mobile/​messages/​index.md Updates message documentation index.
Documentation/​docs-mobile/​building-apps/​build-properties.md Documents the updated type-map property behavior.

Comment thread src/Xamarin.Android.Build.Tasks/Properties/Resources.resx Outdated
@simonrozsival
simonrozsival added this pull request to stack #12891 September 23, 2026 06:57
@simonrozsival
simonrozsival force-pushed the simonrozsival-default-trimmable-typemap branch from da52ffb to 83af087 Compare September 24, 2026 13:26
@simonrozsival simonrozsival changed the title Default Android applications to the trimmable type map Default Android type maps to trimmable Sep 24, 2026
@simonrozsival
simonrozsival force-pushed the simonrozsival-default-trimmable-typemap branch from ea1221b to c422e25 Compare September 25, 2026 21:15
simonrozsival and others added 2 commits September 26, 2026 17:50
Reject the legacy llvm-ir selection with a localized error and cover application and library validation.

Co-authored-by: Copilot App <[email protected]>
Prevent the default from running the app typemap generator in libraries and reject legacy selection before build, publish, and direct generation targets.

Co-authored-by: Copilot App <[email protected]>
@simonrozsival
simonrozsival force-pushed the simonrozsival-default-trimmable-typemap branch from c422e25 to ed8fc18 Compare September 26, 2026 15:55
simonrozsival and others added 2 commits September 26, 2026 22:19
Remove llvm-ir selections from normal test matrices while retaining explicit XA4267 rejection coverage. Preserve newer UCO binding coverage against the default typemap.

Restore shared CoreCLR trimming and opt-in legacy binding fixups, runtime provider generation, typemap packaging metadata, and final-image fast deployment. Preserve reflection-discovered Android tests and update generated-output expectations and measured APK baselines.

Validated locally with 251 passing host cases, focused pre-trimming regressions, 49 passing device integration cases, 773 runtime tests, 38 JCW tests and 2 JNI leak tests. The remaining MAUI FullR2R CI failure was device insufficient storage before app launch.

Co-authored-by: Copilot App <[email protected]>
Avoid collecting both pre-trim and linked typemap DLLs in Debug builds with PublishTrimmed enabled. Exercise clean and incremental packaging with R2R on and off, including the generated type maps for both ABIs.

Validated with 20 passing focused packaging, incremental-build, and typemap tests; four existing unsupported configurations skipped.

Co-authored-by: Copilot App <[email protected]>
@simonrozsival

Copy link
Copy Markdown
Member Author

@dalexsoto review

@simonrozsival

Copy link
Copy Markdown
Member Author

/review

@simonrozsival simonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Sep 27, 2026
@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

✅ Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #12887

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Needs Changes — 1 error and 2 warnings.

The overall trimmable-default integration is comprehensive and CI is green, but incremental builds can retain both obsolete ReadyToRun typemap assemblies and obsolete CoreCLR runtime-provider Java sources when their input sets shrink. The property validation also permits an explicitly empty global value that dispatches to the unsupported LLVM path. I left targeted inline comments for each issue.

Generated by Android PR Reviewer for #12887 · copilot · gpt56 · 1.7K AIC · ⌖ 11.3 AIC · ⊞ 25.9K
Comment /review to run again

Comments that could not be inline-anchored

src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets:326

🤖 ❌ MSBuild targets — This glob republishes every typemap image left in $(IntermediateOutputPath)R2R, not just the current ResolvedFileToPublish/ReadyToRun input set. CreateReadyToRunImages does not prune outputs for assemblies removed from the project, so after removing a binding reference an incremental build can package its stale _*.TypeMap.dll even though a clean build does not. Please derive this list from the current typemap items (or explicitly remove obsolete R2R outputs), …

src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets:544

🤖 ⚠️ MSBuild targets — This task is skipped when the provider set becomes empty, and WriteAdditionalRuntimeProviderSources() only writes current files; neither path deletes providers that disappeared. Removing the last secondary process therefore leaves MonoRuntimeProvider_*.java under android/src, where _FindJavaStubFiles will keep compiling it on incremental builds. The wildcard FileWrites entry also protects that stale file from IncrementalClean. Please run cleanup even for …

src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets:2989

🤖 ⚠️ MSBuild targets — An explicitly empty global property (for example /p:AndroidTypeMapImplementation=) is immutable, so the new conditional default cannot replace it. This condition then accepts the empty value, while the import below treats every non-trimmable value as LLVM and silently selects the implementation the diagnostic says is unsupported. Please reject an empty effective value or normalize dispatch so defaulting, validation, and import selection agree; a command-line-empt…

@dalexsoto dalexsoto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found two blockers in the new default trimmable path:

  1. Removed binding references can leave obsolete typemaps in an incremental APK (src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets:306,321-334). These linked and ReadyToRun directory globs republish every _*.TypeMap.dll still under obj, not the current typemap-assemblies.txt inventory. Removing a binding updates the generated list, but ReadyToRun does not prune the old image; this target can add that removed binding's typemap back to ResolvedFileToPublish and the assembly store. A clean build and an incremental build then package different typemaps. The globs predate this PR, but making this path the default exposes the regression to ordinary CoreCLR builds. Select only current typemap outputs and remove orphaned images, with a remove-reference incremental regression.

  2. An explicitly empty global typemap value bypasses the unsupported-mode diagnostic (src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets:2984-3001; Microsoft.Android.Sdk.DefaultProperties.targets:47). /p:AndroidTypeMapImplementation= is a global property, so the conditional trimmable default cannot replace it. Validation excludes empty values, while the subsequent import selects LLVM for every value other than trimmable. An Android app therefore takes the unsupported LLVM path without XA4267, contrary to this PR's rejection policy. Reject an empty effective value or make validation and import dispatch agree, and test the command-line global-property case.

simonrozsival and others added 2 commits September 28, 2026 21:04
Integrate current main, retaining the supported Crc64 naming-policy cases and trimmable post-link assertions while adopting upstream pruning of unsupported test configurations.

Co-authored-by: Copilot App <[email protected]>
Publish linked typemaps from the current generator inventory and ReadyToRun maps from the SDK's current output items. Remove orphaned linked/R2R typemap images and symbols so removed references cannot reappear in incremental APKs.

Reject explicitly empty global AndroidTypeMapImplementation values rather than silently dispatching the legacy pipeline. Add multi-ABI remove-reference clean/incremental comparisons for R2R on/off and global-property validation regressions.

Both regression groups failed before the fixes and passed afterward; 40 focused regression and compatibility cases passed on merged main.

Co-authored-by: Copilot App <[email protected]>
@simonrozsival

Copy link
Copy Markdown
Member Author

@dalexsoto Addressed both blockers from your review in 066f293, after merging current main in 9c5ecca.

  1. Removed binding references: linked typemap publication now follows typemap-assemblies.txt, and R2R typemaps are selected from the SDK’s current _ReadyToRunFilesToPublish rather than directory globs. Orphaned linked/R2R typemap DLLs and their PDBs are removed. The new regression builds with a binding, removes the reference, checks the APK and intermediate outputs, performs a no-op rebuild, and compares the packaged typemap inventory with a clean build. It covers arm64 and x86_64 with R2R both enabled and disabled. Before the fix, the IL-only case packaged the stale map and the R2R case failed crossgen trying to load the removed binding.

  2. Empty global property: an effective empty AndroidTypeMapImplementation is now rejected by validation. Omitting the property still uses the trimmable default. Added eight command-line global-property cases covering application/library builds, publish, configuration validation, and direct generation/linking entry points; all reproduced the bypass before the fix.

The merged Release SDK build and all 40 focused regression/compatibility cases passed locally. The changes are pushed; fresh CI is pending.

Preserve upstream FastTiming removal and regenerate the four CoreCLR APK-size references from the merged Release SDK.

Co-authored-by: Copilot App <[email protected]>
@simonrozsival simonrozsival added r8-runtime-rewriting ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). and removed ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). r8-runtime-rewriting labels Sep 30, 2026
Include the upstream Android SDK path null checks while preserving the previously resolved FastTiming removal and regenerated APK baselines.

Co-authored-by: Copilot App <[email protected]>
@simonrozsival
simonrozsival merged commit c6563ee into main Sep 30, 2026
44 checks passed
@simonrozsival
simonrozsival deleted the simonrozsival-default-trimmable-typemap branch September 30, 2026 15:56
simonrozsival added a commit that referenced this pull request Oct 3, 2026
Stacked on #12887; **base: `simonrozsival-default-trimmable-typemap`**.

## Summary
- Remove the obsolete llvm-ir typemap targets and generators, native typemap output, marshal-method assembly rewriting, legacy Java-stub tasks, and dead after-link chain. Keep shared application configuration, NativeAOT bootstrap, Java remapping, and the empty native symbols still required until #12890.
- Preserve the trimmable build behavior previously mixed into #12887 as six separate, scoped commits: CoreCLR additional process providers; pre-ILLink abstract-method repair on project-local assembly copies; effective-RID post-trim JCW selection and Proguard inputs; typemap assembly metadata and FastDeploy preference for linked/ReadyToRun DLLs; Java-library categorization before nested D8; and the deserialization callback trimmer root.
- Keep the shared post-ILLink steps, `LinkDescription`, `LinkerDumpDependencies`, and incremental file tracking. Reject `AndroidEnableMarshalMethods=true` with `XA1049` rather than accepting an unsupported path.

The default property and `XA4265` validation remain in the small bottom PR. JVM-only test cleanup is in #12894; this PR does not replay the old bottom branch's broad CI and test-fixture changes.

## Validation
- 1,025 standalone trimmable typemap tests and 11 focused build-task tests passed (7 fixture-dependent cases skipped); the FastDeploy task project built without warnings or errors.
- Changed XML and the target dependency graph were checked, as was the diff.
- Full build-task, host, and device integration tests could not run here without the built in-tree Android SDK and generated `generator.dll`; CI must validate the combined path.
simonrozsival added a commit that referenced this pull request Oct 9, 2026
## Summary

Retire the experimental `JavaInterop1` binding/Java Callable Wrapper (JCW) generation paths and their obsolete runtime infrastructure. Keep **`XAJavaInterop1`** as the supported binding/legacy JCW shape, and keep the trimmable typemap's Java generator as a **separate, targetless pipeline**.

This implements the Android cleanup tracked by [#11727](#11727), including removal of `ManagedPeer`, `GCUserPeerable`, the handwritten Java.Interop proxies, and `java-interop.jar`. Preserve managed-object transport and consolidate exception transport onto the existing generated `Android.Runtime.JavaProxyThrowable` for both CoreCLR and NativeAOT.

The branch is rebased onto `main` commit `058e71b5867c63fd92a00ae545c7899f2b60ed6e`, including the Android test relocation in [#13019](#13019) and the retained-typemap R8 pipeline in [#12829](#12829). This remains a draft; local validation is documented below, not presented as a claim that CI is green. No tracking issue is auto-closed.

## Android compatibility: why this can be removed

### Normal .NET for Android and .NET MAUI codegen is unchanged

The standard .NET for Android application/binding pipeline, also used by .NET MAUI Android applications, uses **`XAJavaInterop1`**, not the experimental `JavaInterop1` shape. This PR preserves that supported code-generation path.

NativeAOT previously selected `JavaInterop1` for its JCWs. Android NativeAOT has remained an experimental runtime option, documented by [XA1040](https://github.com/dotnet/android/blob/e2230e9d5d/Documentation/docs-mobile/messages/xa1040.md), and **merged [#12133](#12133 already switched it to `XAJavaInterop1` and unified the Android GC bridge around `mono.android.IGCUserPeer`. Before this PoC, the SDK defaults already selected `XAJavaInterop1` for both `AndroidCodegenTarget` and `_AndroidJcwCodegenTarget`.

The old `JavaInterop1` option was explicitly documented as **experimental** in the [build-property documentation](https://github.com/dotnet/android/blob/e2230e9d5d/Documentation/docs-mobile/building-apps/build-properties.md#androidcodegentarget). Its historical NativeAOT-default description is stale after #12133 and is corrected here.

The supported Android paths have already migrated:

- [#12133](#12133) migrated NativeAOT's JCW shape and GC bridge.
- [#12152](#12152) removed the built-in proxies' dependency on `ManagedPeer` for native registration.
- [#12887](#12887) made the trimmable typemap the default and rejected legacy typemap selection.
- [#12890](#12890) removed the old native typemap/reflection-backed Android activation path.
- [#13018](#13018) removed the remaining reflection-backed JNI managers. Current Android runtimes use the trimmable managers.

All these prerequisites are merged. No supported current Android code-generation or activation path needs `ManagedPeer.construct` or JavaInterop1-style wrappers. Some obsolete helpers were still packaged or initialized through common runtime plumbing; removing that leftover machinery does not remove the codegen used by normal .NET for Android or .NET MAUI apps.

### Each removed runtime component has an Android replacement or no supported consumer

| Removed component | Android justification / replacement |
|---|---|
| `Java.Interop.ManagedPeer` and `net.dot.jni.ManagedPeer` | No supported generated JCW shape needs their reflective constructor/native-registration trampoline after #12133 and the trimmable migration. Remove initialization, feature-switch plumbing, built-in mappings, scanner exemptions, suppressions, and ProGuard retention together. |
| `net.dot.jni.GCUserPeerable`, `jiAddManagedReference`, and `jiClearManagedReferences` | Android GC processing uses `mono.android.IGCUserPeer`. Migrate surviving handwritten Android fixtures to that contract; remove the obsolete runtime field, host/managed marker lookups, and matching managed/native initialization-struct field. Android GC/reference tracking is retained. |
| `Java.Interop.JavaProxyObject` and `net.dot.jni.internal.JavaProxyObject` | No consumer remains in the active Android value-manager path. Keep the existing `TrimmableJavaProxyObject` and its Java identity semantics; the removed handwritten class is not its implementation. |
| `Java.Interop.JavaProxyThrowable` and `net.dot.jni.internal.JavaProxyThrowable` | NativeAOT still used this exception-transport path, so it is **replaced before removal**, not simply deleted as unused. Route NativeAOT through the existing generated `Android.Runtime.JavaProxyThrowable`, as CoreCLR already did. No third `TrimmableJavaProxyThrowable` is introduced. |
| `java-interop.jar` | After deleting its obsolete helpers, it has no remaining supported Android runtime content. Remove build/install wiring and references. Android fixture compilation references `android.jar` and `java_runtime.jar` instead. |
| Handwritten `SelfRegistration`, `TestType`, `TestTypeTests`, and the old Java.Interop `CallNonvirtualDerived2` fixture | These were unused or depended on the retired Java-to-managed constructor activation path; the obsolete test class was already excluded by capability guards. Keep the separate live generated-binding class with the same short name and preserve supported constructor/dispatch/exception scenarios. |

The removed managed runtime implementation types are internal. Public `JavaObject`, `JavaException`, `JniPeerMembers`, and `IJavaPeerable` remain. The existing Android throwable and managed-object proxy implementations also remain. The explicit public configuration-property removal below is documented separately.

### Supported construction and registration remain intact

Ordinary managed-to-Java construction does not need `ManagedPeer`: `JavaObject` allocates the Java object, associates the managed peer, then invokes the Java constructor through `StartCreateInstance`/`FinishCreateInstance`.

For Java-to-managed activation and callbacks, supported Android types use generated Android/trimmable dispatch and registration. The trimmable generator registers natives through `TrimmableTypeMap.RegisterNativeMethods()` / `mono.android.Runtime.registerNatives(Class)`, independently of either removed `JavaInterop1` enum value. No reflective compatibility shim is reintroduced.

## Implementation changes

### Generator and build surface

- Remove `CodeGenerationTarget.JavaInterop1`, `JavaPeerStyle.JavaInterop1`, their generator/source-writer/JCW branches, the `javainterop1` CLI selector, and unused SDK configuration selecting that shape.
- Preserve `XAJavaInterop1` output and the independent trimmable pipeline. This is not a rename of the typemap generator to another `AndroidCodegenTarget` value.
- Reject retired `AndroidCodegenTarget` values with actionable **XA4232** diagnostics for application and library builds. Retain the existing **XA4240** rejection for retired `_AndroidJcwCodegenTarget` values, and test rejection rather than silently falling back.
- Remove retired expected-output trees and conditional `JAVA_INTEROP1`/`TODO_JAVA_INTEROP1`/`TryJavaInterop1` scaffolding. Move shared XML inputs/support sources from `expected.ji` into `TestInputs` so supported tests do not depend on a retired baseline directory.
- Update directly related build-property/error documentation and the test catalog.

### Shared Android exception handling

Add a shared internal `AndroidExceptionManager` and make CoreCLR and NativeAOT delegate wrapping/unwrapping to it. `JniEnvironment.Exceptions.Throw(Exception)` dispatches to the active runtime's `RaisePendingException` implementation.

The Android implementation wraps ordinary managed exceptions in the generated Android proxy, recognizes existing Java exception peers, and returns the original managed exception when its proxy returns through JNI or appears as a Java cause. Typed value-manager lookup distinguishes peer from unboxed exception: requests for `Java.Lang.Throwable`, `JavaException`, or `IJavaPeerable` return the Java peer; requests for compatible managed exception/object types return the original managed value.

Shared lookup releases incoming JNI references in an option-aware `finally`, on successful and failing extraction paths. Borrowed references remain valid; transferred local/global references are released exactly once. Permanent regressions cover typed lookup and four extraction failure stages across all three ownership modes.

The base `JniRuntime.RaisePendingException` still throws an existing `JavaException`. Android overrides supply managed-exception wrapping; a runtime without an implementation receives an explicit `NotSupportedException` instead of silently depending on a deleted proxy. The exception contract is documented.

### Finish removing associated scaffolding

- **R8 roots:** remove eight keep entries for the four retired Java helpers from `proguard_typemap.cfg` and `proguard_typemap_coreclr.cfg`, along with their misleading comments. Preserve Android bootstrap/GC roots. Update the existing R8 response tests to assert that neither retained-typemap mode emits retired-helper roots.
- **Registration metadata:** retain `JniAddNativeMethodRegistrationAttribute` with warning-only `[Obsolete]`, explicitly stating that it has no effect. Delete the unused public `JniRuntime.CreationOptions.JniAddNativeMethodRegistrationAttributePresent` property rather than retaining a no-op setting; record its accessor removals with `*REMOVED*` entries in `PublicAPI.Unshipped.txt`. Its unread internal copy and Android assignment are also gone.
- **Dead scanner/diagnostic code:** remove the attribute-specific metadata pre-scan/flag, unused checker and commented rejection block, and unused **XA4251** logger plumbing. Remove XA4251 from the English resource and its designer accessor. Other live metadata matching helpers remain.
- **AOT profile:** delete the unreferenced `custom.aotprofile` ProjectTools fixture containing removed `ManagedPeer.Init`/`.cctor` records. Source searches found no direct or dynamically enumerated profile consumer; the resource wildcard was its remaining inclusion path. Rebuilt-assembly inspection confirms it is no longer embedded. Do not regenerate a profile for retired functionality.

No non-English `.resx` or generated localization files are modified.

### Reconciliation with current main

Preserve #13019's local C# compilation and Java fixture glob under `tests/Mono.Android-Tests/Java.Interop-Tests/`, including its `AndroidJavaSource` exclusion to prevent duplicate compilation. Apply fixture deletions and `IGCUserPeer` conversions at the relocated paths; do not restore the external test source directory/globs. Keep the Android-specific `GetThis` implementation and comments.

Preserve upstream native host startup/logging and assembly-store changes. Preserve #12829's retained-key R8 pipeline; do not reintroduce DGML generation or alter new targets to accommodate the old validation setup.

## Android coverage preserved and added

- **All 23** former `expected.ji` integration scenarios retain XA baselines and active runners. Restore `Java_Lang_Object` against XA and refresh its previously unused snapshot. Shared inputs are migrated, not deleted.
- Retained Common/XA/NRT baselines cover 71 of 72 retired unit-baseline names; the unmatched `WriteInterfaceFields` snapshot already had no test consumer before cleanup.
- Existing Android `ConstructorActivationTests`, generated-binding `BindingTests.VirtualMethodBinding`, and `ExceptionTests.ManagedJavaManaged_FinallyExecuted` retain activation, multi-level dispatch, and managed/Java/managed exceptions with Java `finally` execution.
- Port array-mutation scenarios to a real Java fixture and generated XA bindings: `int[]`, `int[][]`, `int[][][]`, with null/invalid/success results and complete copy-back assertions. These exercise generated `JNIEnv.CopyArray`, not handwritten replacement marshalling.
- Add GC-peer contract coverage for surviving handwritten fixtures and the active managed-object proxy, invoking actual add/clear operations.
- Add/strengthen exception identity, causes, unregistered throwable, Java-visible stack trace, callback identity, typed lookup, and throwable-array coverage.
- Add **17 permanent regressions** for typed/unboxed lookup and transferred-reference failures. All 17 passed on both runtimes after rebase, without skips.
- Require the generated Android throwable in both runtimes' packaging/R8 tests and reject old proxies. Existing R8 response tests now also reject all four obsolete helper roots in both rule modes.

Most deletions are retired generated baselines and duplicate scaffolding, not supported Android runtime scenarios.

## Local validation

### Latest property/scanner/resource/profile cleanup

The following checks ran against the final cleanup sources with a rebuilt Debug Android SDK. These are distinct from the earlier complete post-rebase device runs below.

| Command/check | Result |
|---|---|
| `bin/Debug/dotnet/dotnet build external/Java.Interop/src/Java.Interop/Java.Interop.csproj -c Debug -v quiet -p:JavaCPath=/usr/bin/javac -p:JarPath=/usr/bin/jar` and the same command with `-c Release` | **Passed**, 0 warnings/errors in both configurations; public API removal records accepted. |
| `bin/Debug/dotnet/dotnet build src/Mono.Android/Mono.Android.csproj -c Debug -v quiet` | **Passed**, 0 errors; no remaining source consumer of the deleted option. |
| `bin/Debug/dotnet/dotnet build build-tools/scripts/Prepare.proj -t:BuildDotNet -c Debug -v quiet` with Debug workload roots | **Passed**, 0 errors; existing warnings. An initial overlapping scanner-test build locked a shared intermediate DLL (MSB4018); the SDK retry passed after that build completed, without source workarounds. |
| `bin/Debug/dotnet/dotnet test src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/Microsoft.Android.Build.Tasks.Tests.csproj -c Debug -v quiet --filter 'FullyQualifiedName~TrimmableTypeMap\|FullyQualifiedName~TypeMapProguardTargetsTests'` | **73 passed, 4 existing prerequisite skips, 0 failures**. The filter uses the literal `|` OR operator. |
| `bin/Debug/dotnet/dotnet build src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj -c Debug -v quiet` | **Passed**, 0 errors; rebuilt task/resource and ProjectTools consumers. |
| Metadata inspection of rebuilt Debug/Release `Java.Interop.dll` and `Xamarin.ProjectTools.dll` | **Passed**: option absent from both configurations, attribute retained with warning-only `Obsolete`, deleted profile absent from manifest resources. |
| English resource XML parse, active source/reference checks, `git diff --check` | **Passed**; obsolete roots/diagnostic machinery absent and localization boundaries preserved. |

This combined invocation **passed 36 cases with 0 skips/failures**: all R8 task cases, both retained-rule R8 integration modes, CoreCLR/NativeAOT throwable packaging, and ProGuard builds.

```sh
bin/Debug/dotnet/dotnet test bin/TestDebug/net10.0/Xamarin.Android.Build.Tests.dll \
  --filter 'FullyQualifiedName~R8Tests|Name~RetainedTypeMapRulesDriveR8|Name~TrimmableTypeMap_PackagesAndroidJavaProxyThrowable|Name~BuildProguardEnabledProject' \
  -v quiet
```

### Full post-rebase runtime/device validation

Both configuration-specific SDKs were rebuilt after the rebase using `Prepare.proj -t:BuildDotNet -c Debug/Release`. Set matching `DOTNETSDK_WORKLOAD_MANIFEST_ROOTS="$PWD/bin/<configuration>/lib/sdk-manifests"` and `DOTNETSDK_WORKLOAD_PACK_ROOTS="$PWD/bin/<configuration>/lib"`, with `ANDROID_SERIAL=emulator-5554`. Explicit SDK paths avoid `dotnet-local.sh` preferring an existing Release SDK over Debug.

```sh
# Debug workload roots, from repository root.
bin/Debug/dotnet/dotnet build -t:Install -c Debug \
  tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj \
  -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=CoreCLR \
  -p:RuntimeIdentifier=android-arm64 '-p:AdbTarget=-s emulator-5554' -v quiet
(
  cd tests/Mono.Android-Tests/Mono.Android-Tests
  ../../../bin/Debug/dotnet/dotnet test Mono.Android.NET-Tests.csproj \
    --no-build -c Debug --device emulator-5554 --report-trx \
    --results-directory ../../../bin/TestDebug/RebaseResults \
    -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=CoreCLR \
    -p:RuntimeIdentifier=android-arm64 '-p:AdbTarget=-s emulator-5554'
)
```

**Passed: 897 CoreCLR tests, 7 existing skips, 0 failures**, including relocated JNI tests and all 17 permanent regressions. This full CoreCLR suite also passed again during the subsequent deprecation step (results under `ObsoleteRegistrationResults`), before the latest host-only scanner/root/profile cleanup.

```sh
# Release workload roots, from repository root.
bin/Release/dotnet/dotnet build -t:Install -c Release \
  tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj \
  -m:1 -p:BuildInParallel=false \
  -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=NativeAOT \
  -p:PublishAot=true -p:RuntimeIdentifier=android-arm64 \
  -p:_AndroidEnableTypemapR8Trimming=true -p:_SkipNdkResolution=false \
  '-p:AdbTarget=-s emulator-5554' -v quiet
(
  cd tests/Mono.Android-Tests/Mono.Android-Tests
  ../../../bin/Release/dotnet/dotnet test Mono.Android.NET-Tests.csproj \
    --no-build -c Release --device emulator-5554 --report-trx \
    --results-directory ../../../bin/TestRelease/RebaseResults \
    -p:BuildInParallel=false \
    -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=NativeAOT \
    -p:PublishAot=true -p:RuntimeIdentifier=android-arm64 \
    -p:_AndroidEnableTypemapR8Trimming=true -p:_SkipNdkResolution=false \
    '-p:AdbTarget=-s emulator-5554'
)
```

**Passed: 842 NativeAOT tests, 7 existing skips, 0 failures**, including all 17 regressions. The current retained-key R8 pipeline produced **328 retained keys**, including the Android throwable; `r8.json` confirmed shrinking enabled. No old native-output-path override or DGML workaround was used, and no trimming/R8/AOT was disabled. Build-only `-m:1` is omitted from the test invocation because MTP rejects the forwarded `--m` option.

These complete device suites were not rerun for the latest scanner/resource/profile-only additions; the latest 36-case host integration run builds and verifies both CoreCLR and NativeAOT packages against the updated resources.

### Other post-rebase tooling and generated-XA coverage

| Command/check | Recorded result |
|---|---|
| `bin/Debug/dotnet/dotnet test external/Java.Interop/tests/generator-Tests/generator-Tests.csproj -c Debug -v quiet -p:JavaCPath=/usr/bin/javac -p:JarPath=/usr/bin/jar` | **478 passed**, 0 skipped. |
| Same command shape for `external/Java.Interop/tests/Java.Interop.Tools.JavaCallableWrappers-Tests/Java.Interop.Tools.JavaCallableWrappers-Tests.csproj` | **50 passed**, 0 skipped. |
| Same command shape for `external/Java.Interop/tests/Java.Interop.Tools.Generator-Tests/Java.Interop.Tools.Generator-Tests.csproj` | **18 passed**, 0 skipped. |
| `bin/Debug/dotnet/dotnet test src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/Microsoft.Android.Build.Tasks.Tests.csproj -c Debug -v quiet --filter FullyQualifiedName~GenerateTrimmableTypeMapTests` | **16 passed**, 0 skipped after rebase; superseded by the broader latest 73-case run. |
| `bin/Debug/dotnet/dotnet test bin/TestDebug/net10.0/Xamarin.Android.Build.Tests.dll --filter 'FullyQualifiedName~InvalidConfigTests\|Name~TrimmableTypeMap_PackagesAndroidJavaProxyThrowable\|Name~BuildProguardEnabledProject\|Name~RetainedTypeMapRulesDriveR8' -v quiet` | **58 passed**, 0 skips/failures after rebase; latest related resource/packaging coverage is the 36-case run above. |

Generated-XA JCW device commands used `tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.csproj` with `build -t:Install`, then matching `test --no-build` from its directory, `--device emulator-5554 --report-trx`, and configuration-specific workload roots:

- **CoreCLR:** `-c Debug -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=CoreCLR -p:AndroidCodegenTarget=XAJavaInterop1 -p:RuntimeIdentifier=android-arm64 '-p:AdbTarget=-s emulator-5554'` — **46 passed, 2 existing explicit skips**, including arrays, dispatch, and managed/Java/finally exceptions.
- **NativeAOT arrays:** `-c Release -p:BuildInParallel=false -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=NativeAOT -p:PublishAot=true -p:AndroidCodegenTarget=XAJavaInterop1 -p:RuntimeIdentifier=android-arm64 -p:IncludeCategories=ArrayMutation -p:_AndroidEnableTypemapR8Trimming=true -p:_SkipNdkResolution=false '-p:AdbTarget=-s emulator-5554'`, plus build-only `-m:1` — **3 passed, 0 skips/failures**, with retained-key R8 trimming enabled.

Post-rebase artifact inspection verified all **10** Debug/Release runtime JARs omit the retired helpers, `java-interop.jar` is absent, the relocated fixture JAR omits deleted fixtures, and both signed runtime-test APKs contain the Android throwable without the retired helper descriptors.

Device coverage is arm64 only, not a complete ABI/device matrix. No CI success is claimed.

## Compatibility and follow-ups

- Old Java wrappers/handwritten code explicitly using the retired experimental `ManagedPeer`/JavaInterop1 contract must be regenerated/migrated. No compatibility shim is retained. [#11635](#11635) tracks that question; #11727 documents the removal direction. Normal .NET for Android/MAUI codegen remains intact.
- The unused public `JniAddNativeMethodRegistrationAttributePresent` configuration property is intentionally removed; the attribute itself remains warning-only obsolete. Existing code setting that property must remove the setting, which no longer controlled any Android runtime behavior.
- Developers should clean old intermediates when changing between pre-/post-removal trees. No permanent build invalidation/cleanup machinery is added solely for this transition.
- Broader single-target generator-plumbing simplification and other legacy public API deprecations/removals are separate follow-ups, not mixed into this PR.
- Successful shared exception lookup could additionally receive borrowed/global-reference success tests, complementing the existing failure matrix; this optional coverage is not claimed as implemented.

## Related issues and PRs

- [#11727](#11727): experimental JavaInterop1 retirement tracker.
- [#11635](#11635): old ManagedPeer bytecode compatibility discussion.
- [#12133](#12133), [#12152](#12152): merged NativeAOT/GC migration and proxy decoupling.
- [#12887](#12887), [#12890](#12890): merged trimmable default and legacy activation/native typemap removal.
- [#13018](#13018): merged reflection-manager removal.
- [#13019](#13019): merged Android test relocation, reconciled here.
- [#12829](#12829): retained-key R8 pipeline replacing DGML, exercised by validation and preserved by this cleanup.
- [#11480](#11480): **closed, unmerged** earlier trimmable-only ManagedPeer removal; useful artifact-check precedent, not a claimed prerequisite. No performance numbers from it are claimed here.
- [dotnet/java-interop#1490](dotnet/java-interop#1490): **closed, unmerged** ManagedPeer registration modernization; unnecessary once those Android consumers are removed.

----

Pull Request
[title](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md#commit-summary) and
[description](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md#commit-body)
should follow the
[`commit-messages.md` workflow documentation](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md), and in particular should include:

- [x] Useful description of *why the change is necessary*.
- [x] Links to issues fixed
- [x] Unit tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants