Skip to content

[Android] AccessibilityBridge's API-34 high-contrast lambda fails class verification on pre-34 devices (NoClassDefFoundError at launch) #192231

Closed

Description

@andreu-puy

Steps to reproduce

Steps 1-4 are deterministic on any machine and prove the defect is in every release build.
Step 5 is the device-dependent manifestation — the reason this reaches you as a crash report
rather than a local repro.

1. Any Flutter app on 3.47.0 or later (reproduced on 3.47.0 and 3.47.2), built for Android
   with minSdk 24, compileSdk 36, AGP 9.1.0, R8 enabled (i.e. a stock `--release` build).

2. flutter build appbundle --release

3. Find the obfuscated name of AccessibilityBridge and its synthetic lambdas in
   build/app/outputs/mapping/release/mapping.txt:

     io.flutter.view.AccessibilityBridge -> io.flutter.view.a
     io.flutter.view.AccessibilityBridge$$ExternalSyntheticLambda1 -> t2

4. Extract the DEX from the AAB and disassemble the constructor:

     dexdump -d classes4.dex   # the DEX containing io.flutter.view.a

   Observe in AccessibilityBridge.<init>:

     sget          v0, Landroid/os/Build$VERSION;.SDK_INT:I
     const/16      v1, #int 34
     if-lt         v0, v1, 0040
     003a: new-instance  v0, Lt2;        <-- API-34 lambda, never executes below 34
     0040: new-instance  v0, Ll;         <-- the no-op lambda
     0045: iput-object   v0, v2, Lio/flutter/view/a;.<field>:Lb3;   <-- the type join

   Lt2; declares Landroid/app/UiModeManager$ContrastChangeListener; (API 34) and has
   onContrastChanged(F)V. No stub for that interface exists in any DEX in the bundle.

5. Run that build on a pre-34 device whose ART eagerly resolves the type join at 0045.
   The app dies at launch. Not every pre-34 device does this - most ART builds tolerate the
   unresolved join - which is why this presents as a small, persistent slice of users rather
   than a universal failure.

Expected results

The app launches. A version-gated branch that never executes on the device should not prevent
io.flutter.view.AccessibilityBridge from loading, and AccessibilityBridge. should contain
no API-34 types in its verifiable body.

Actual results

The app cannot launch at all on an affected device. FlutterView.attachToFlutterEngine constructs
AccessibilityBridge unconditionally, so the process dies in Activity.performStart before a single
line of Dart runs. No accessibility service needs to be enabled.

Fatal Exception: java.lang.NoClassDefFoundError: Failed resolution of: Lt2;
at io.flutter.view.AccessibilityBridge.(AccessibilityBridge.java:428)
at io.flutter.view.AccessibilityBridge.(AccessibilityBridge.java:447)
at io.flutter.embedding.android.FlutterView.attachToFlutterEngine(FlutterView.java:1225)
at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onCreateView(...:437)
at io.flutter.embedding.android.FlutterFragment.onCreateView(FlutterFragment.java:1121)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:3115)
...
at android.app.Activity.performStart(Activity.java:8330)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3670)

Caused by java.lang.ClassNotFoundException: t2

Device is Android 12/13 by the frames (Looper.loopOnce:201 / loop:288,
ActivityThread.main:7872, and jdk.internal.misc.Unsafe, which libcore moved to in S).

Mechanism: ART resolves the synthetic while verifying the type join of the ternary's two
branches at the iput-object, even though the if-lt means the API-34 branch never executes. The
superinterface does not exist below API 34, so DexFile.defineClass fails - and because it
swallows the NoClassDefFoundError into suppressed and returns null, the loader surfaces it as
ClassNotFoundException.

Seen on four consecutive releases of a production app. Lt2; is only R8's name for the
synthetic and varies per build; an earlier release crashed as Lio/flutter/view/a; - same
failure, different name.

Code sample

The form asks for a flutter create bug reproduction. There isn't a Dart-level one: the defect
is in the engine's Java and manifests only on specific ART builds. Say so explicitly and give the
engine source instead — that is what a triager needs here.

No Dart-level reproduction is possible: the defect is in the engine's Android embedding, and any
Flutter app built in release mode carries it. The offending code is:

// engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java:426-432
private final ContrastChangedListener highContrastObserver =
    (Build.VERSION.SDK_INT >= API_LEVELS.API_34)
        ? (ContrastChangedListener & UiModeManager.ContrastChangeListener)
            contrast -> setHighContrastFlag()
        : contrast -> {
          /* no-op */
        };

The SDK_INT guard is correct, and registerHighContrastObserver (:718) is properly annotated
@RequiresApi(API_LEVELS.API_34). The bug is that the guarded TYPE appears inside <init>'s
verifiable body: the intersection cast

    (ContrastChangedListener & UiModeManager.ContrastChangeListener)

forces D8's desugared lambda to implement the API-34 interface, and the ternary puts it in a
type join the verifier must check on every device.

PROPOSED FIX - the precedent is already in this file. AccessibilityBridge gained an Api31Impl
holder in #184630 (8ad7de9c41e) for the same class of bug, and its javadoc states exactly the
rule this initializer breaks:

  /**
   * Isolates API-31 field references so that ART's class verifier does not attempt to resolve
   * them when loading {@link AccessibilityBridge} on older API levels. Without this separation,
   * the verifier eagerly resolves {@link Configuration#fontWeightAdjustment} at class-load time,
   * causing a {@link NoSuchFieldError} crash on Android 11 devices (observed on Pixel 4a and
   * OnePlus 8 Pro). This mirrors the fix AndroidX Compose applied for the same crash (AOSP
   * b/353988277).
   */
  @RequiresApi(API_LEVELS.API_31)
  private static class Api31Impl { ... }

That isolation was urgent enough to be cherry-picked into the 3.44 stable line. The high-contrast
lambda - added later, in #182263 (engine 8bd5cd5e919, 2026-04-29) - reintroduces precisely the
pattern the comment warns against, one API level up.

The fix is the same shape, an @RequiresApi(34) holder so no API-34 type reaches <init>:

  @RequiresApi(API_LEVELS.API_34)
  private static class Api34Impl {
    @DoNotInline
    static ContrastChangedListener newHighContrastObserver(AccessibilityBridge bridge) {
      return (ContrastChangedListener & UiModeManager.ContrastChangeListener)
          contrast -> bridge.setHighContrastFlag();
    }
  }

with the field initialised to the no-op lambda and assigned from Api34Impl behind the existing
SDK_INT >= 34 check. registerHighContrastObserver also casts the field back to
UiModeManager.ContrastChangeListener at :723, so that cast moves into the holder too - it is
already inside an @RequiresApi(34) method, so the change is mechanical.

AFFECTED VERSIONS: 3.47.0, 3.47.1, 3.47.2, 3.48.0-0.1/0.2/0.3.pre, and master (lines 426-432
verbatim). NOT 3.44.9, which predates #182263. Verified by tag: `Api34Impl` count is 0 in all of
the above; `highContrastObserver` count is 0 at 3.44.9 and 3 at every 3.47/3.48 tag.

Screenshots or Video

Screenshots / Video demonstration

[Upload media here]

Logs

Two local flutter build appbundle --release runs, one with the shipped R8 config and one with
-keep class io.flutter.view.** { *; } plus android.enableR8.fullMode=false, establish that
this is a verification failure and not a shrinker failure:

  • Shipped config, mapping.txt:
    io.flutter.view.AccessibilityBridge$$ExternalSyntheticLambda1 -> t2
    io.flutter.view.AccessibilityBridge -> io.flutter.view.a

  • Keep-rule build removes nothing (usage.txt: zero AccessibilityBridge entries) and only renames
    the synthetic to io.flutter.view.a - which is exactly the name an earlier release crashed with.
    It changes the name in the crash message, not whether the class links.

Workarounds tried, all ineffective:

-keep class io.flutter.view.** { *; } No effect; the class was never removed.
android.enableR8.fullMode=false No effect; not a shrinking problem.
Upgrade Flutter No effect; Api34Impl absent from all stables + master.
Avoid constructing AccessibilityBridge Not possible; attachToFlutterEngine always builds it.
try/catch around the attach No seam in the embedding, and it leaves no UI.
Downgrade to 3.44.9 Works, but it is a three-minor rollback.

Note for whoever triages: compileSdk = 36, so UiModeManager$ContrastChangeListener WAS present in
the android.jar this compiled against - R8's API modeling had the information needed to outline
the synthetic and did not. That is being filed separately against AGP/R8, but the Flutter-side
holder fixes this regardless of what R8 does, and is the fix this file already uses one API level
down.

Flutter Doctor output

[√] Flutter (Channel stable, 3.47.2, on Microsoft Windows [Version 10.0.26200.9278], locale en-GB) [631ms]
• Flutter version 3.47.2 on channel stable at C:\Users\andre\Documents\Flutter\flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision d3b14c8 (8 days ago), 2026-08-26 16:07:51 -0700
• Engine revision a804b26
• Dart version 3.13.2
• DevTools version 2.60.0

[√] Windows Version (Windows 11 or higher, 25H2, 2009) [2.6s]

[√] Android toolchain - develop for Android devices (Android SDK version 36.0.0) [6.4s]
• Android SDK at C:\Users\andre\AppData\Local\Android\sdk
• Platform android-36, build-tools 36.0.0
• Java version OpenJDK Runtime Environment (build 21.0.6+-13368085-b895.109)
• All Android licenses accepted.

[√] Chrome - develop for the web [208ms]
[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.14.13 (August 2025)) [206ms]
[√] Connected device (3 available) [769ms]
[√] Network resources [639ms]

• No issues found!

Activity

  1. added
    platform-androidAndroid applications specifically
    a: accessibilityAccessibility, e.g. VoiceOver or TalkBack. (aka a11y)
    team-accessibilityOwned by Framework Accessibility team (i.e. responsible for accessibility code in flutter/flutter)
    c: crashStack traces logged to the console
    engineflutter/engine related. See also e: labels.
    has reproducible stepsThe issue has been confirmed reproducible and is ready to work on
    fyi-androidFor the attention of Android platform team
    on Sep 3, 2026
  2. mbcorona commented on Sep 3, 2026

    @mbcorona
    Member

    Hi @andreu-puy, thanks for the clear report and the repro steps.

    Routing to the accessibility team for further investigation.

  3. added
    team-androidOwned by Android platform team
    fyi-accessibilityFor the attention of Framework Accessibility team
    P1High-priority issues at the top of the work list
    triaged-androidTriaged by Android platform team
    and removed
    fyi-androidFor the attention of Android platform team
    team-accessibilityOwned by Framework Accessibility team (i.e. responsible for accessibility code in flutter/flutter)
    on Sep 8, 2026
  4. github-actions commented on Sep 25, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  5. locked as resolved and limited conversation to collaborators on Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority issues at the top of the work lista: accessibilityAccessibility, e.g. VoiceOver or TalkBack. (aka a11y)c: crashStack traces logged to the consoleengineflutter/engine related. See also e: labels.fyi-accessibilityFor the attention of Framework Accessibility teamhas reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-androidAndroid applications specificallyteam-androidOwned by Android platform teamtriaged-accessibilityTriaged by Framework Accessibility teamtriaged-androidTriaged by Android platform team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions