Sitelet https://github.com/flutter/flutter/pull/192249
Skip to content

[Android] Request unbuffered dispatch on MotionEvent.ACTION_MOVE for HCPP platform views - #192249

Closed
gmackall wants to merge 2 commits into
flutter:masterfrom
gmackall:unbuffered-pv-scrolling
Closed

gmackall wants to merge 2 commits into
flutter:masterfrom
gmackall:unbuffered-pv-scrolling

Conversation

@gmackall

@gmackall gmackall commented Sep 3, 2026 •

Copy link
Copy Markdown
Member

It makes scrolling a lot smoother 👍

AI pr description below

Description

When scrolling a Flutter scrollable by dragging across an embedded platform view, the mutator view was not calling requestUnbufferedDispatch(event) (unlike FlutterView, which calls it on every touch). As a result, Android's input pipeline kept the touch stream batched to VSYNC (typically 60Hz), making scrolling over platform views feel noticeably less smooth or lower-FPS compared to scrolling over regular Flutter content.

This PR adds support to request unbuffered dispatch on MotionEvent.ACTION_MOVE for HCPP platform views:

  • Scoped to HCPP: Configured via unbufferOnMove on FlutterMutatorView, which defaults to false and is explicitly enabled in PlatformViewsController2 (HCPP). Legacy Hybrid Composition (PlatformViewsController) and Texture Layer (PlatformViewWrapper) remain completely untouched to avoid breaking changes for existing modes.
  • Move only: Gating on MotionEvent.ACTION_MOVE ensures that stationary taps never request unbuffered delivery, protecting native tap recognizers from capacitive digitizer micro-jitter.

Tests

  • Added Robolectric unit tests in FlutterMutatorViewTest.java verifying unbuffered dispatch is requested on ACTION_MOVE when unbufferOnMove is enabled, and not on ACTION_DOWN or ACTION_UP or when disabled.
  • Added assertions in PlatformViewsController2Test.java and PlatformViewsControllerTest.java verifying that HCPP enables unbufferOnMove and legacy Hybrid Composition keeps it disabled.

Generated with Gemini / AI assistance.

@gmackall gmackall added the CICD Run CI/CD label Sep 3, 2026
@github-actions github-actions Bot added platform-android Android applications specifically engine flutter/engine related. See also e: labels. team-android Owned by Android platform team and removed CICD Run CI/CD labels Sep 3, 2026
@gmackall gmackall added the CICD Run CI/CD label Sep 3, 2026
@gmackall
gmackall marked this pull request as ready for review September 3, 2026 20:09
@gmackall
gmackall requested a review from a team as a code owner September 3, 2026 20:09
@gmackall
gmackall requested review from mboetger and reidbaker and removed request for a team September 3, 2026 20:09

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces an option unbufferOnMove to FlutterMutatorView to request unbuffered dispatch on Android ACTION_MOVE touch events, which is enabled in PlatformViewsController2. The review feedback suggests optimizing performance by tracking whether requestUnbufferedDispatch has already been called during the current gesture to avoid redundant JNI calls on every move event, and updating the tests to verify this behavior.

@gmackall
gmackall marked this pull request as draft September 3, 2026 20:38
@flutter-dashboard

Copy link
Copy Markdown

This pull request has been changed to a draft. The currently pending flutter-gold status will not be able to resolve until a new commit is pushed or the change is marked ready for review again.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

@gmackall

gmackall commented Sep 3, 2026

Copy link
Copy Markdown
Member Author

I'm going to close this, I think #192259 is the better approach

@gmackall gmackall closed this Sep 3, 2026
pull Bot pushed a commit to edisplay/flutter that referenced this pull request Sep 15, 2026
…latformViewGestureRecognizer` rejects gesture (flutter#192259)

Changes platform view touch input for all modes to request the
unbuffered stream for the given event when the GestureArena determines
the platform view has bowed out of ownership of that event, via
[View.requestUnbufferedDispatch()](https://developer.android.com/reference/kotlin/android/view/View#requestunbuffereddispatch).
Flutter content itself makes this request, but currently platform views
don't.

This greatly improves platform view scrolling smoothness, while having
no breaking change or impact on platform views performing gesture
handling (because the GestureArena has determined that they don't own
the input event).

### Problem & Invariant
Under high-load or scrolling scenarios, buffered touch dispatch causes
visible jank and input lag. Flutter requests unbuffered dispatch for its
own rendering, but platform views continue using buffered streams unless
requested. Simply requesting unbuffered dispatch unconditionally can
cause issues if gestures are still competing or owned by the platform
view. Scoping unbuffered dispatch to gestures that Flutter has won
ensures that platform views receive fast, unbuffered dispatch
specifically during Flutter gestures (like scrolling).

### Architecture & Gesture Correlation (`embedderId`)
Previous iterations attempted to correlate gestures across the channel
by matching synthetic `downTime` timestamps packed into
`PointerDownEvent.timeStamp`. However, resamplers or clock drift can
alter event timestamps.

Instead, this PR uses Flutter's canonical `embedderId` mechanism:
1. **Android Touch Processor**: When an Android `MotionEvent` arrives,
`AndroidTouchProcessor` tracks it using
`MotionEventTracker.track(event)` and populates
`packet.putLong(motionEventId)`, which the framework receives as
`PointerEvent.embedderId`.
2. **Framework Arena Resolution**: `_PlatformViewGestureRecognizer`
records the `embedderId` of the pointer-down event. When Flutter wins
the arena and calls `rejectGesture(pointer)`, the framework sends
`gestureId` (set to `embedderId`) over the `flutter/platform_views`
system channel.
3. **Engine Correlation (`MotionEventTracker.peek`)**:
- `PlatformViewsController` and `PlatformViewsController2` handle
`onRejectGesture(viewId, gestureId)`.
- The controllers query
`motionEventTracker.peek(MotionEventId.from(gestureId))` to look up the
native `MotionEvent` without consuming it from the tracker.
- From this `MotionEvent`, the native `downTime` is retrieved and passed
to `FlutterMutatorView` and `PlatformViewWrapper`.
- `PlatformViewGestureTracker` marks `flutterWonGesture = true` if the
`downTime` matches the current active gesture stream, and keeps calling
`requestUnbufferedDispatch()` for all remaining events in the gesture
until `ACTION_UP` or `ACTION_CANCEL`.
4. **Graceful Degradation / Fails Closed**:
- If `peek()` returns `null` (e.g. if the event was already popped or on
non-Android embedders), or if `gestureId` is 0 or unmatched, the
optimization degrades gracefully to standard buffered touch dispatch
without breaking input handling.

### Tests
- **Framework tests**:
`packages/flutter/test/rendering/platform_view_test.dart`
- Verifies `rejectGesture` passes the pointer's `embedderId` as
`gestureId`.
- Verifies multi-touch gestures correctly pass individual `embedderId`s.
  - Verifies no channel call is dispatched when `embedderId` is 0.
- **Engine Robolectric tests**:
- `PlatformViewsControllerTest`: Verifies `rejectGesture` with valid
`gestureId` looks up tracked `MotionEvent` and enables unbuffered
dispatch; verifies mismatched or missing IDs do not enable it.
- `PlatformViewsController2Test`: Verifies same behavior on the modern
hybrid composition pipeline.
- `FlutterMutatorViewTest` and `PlatformViewWrapperTest`: Verifies
gesture tracking across `ACTION_DOWN`, `ACTION_MOVE` sequences, and
resetting on `ACTION_UP`/`ACTION_CANCEL`.

Supersedes flutter#192249

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant in-code documentation (doc comments with
`///`).
- [x] If this PR introduces a new feature or capability, I created and
linked a website documentation issue or PR in [flutter/website] (or
verified none is needed).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[flutter/website]: https://github.com/flutter/website
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---
*Generated with Gemini / AI assistance.*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD engine flutter/engine related. See also e: labels. platform-android Android applications specifically team-android Owned by Android platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant