mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
fix: check both pointer count and action before reusing MotionEvent (#178528)
Fix issue https://github.com/flutter/flutter/issues/169486 ## Problem After [PR #178015](https://github.com/flutter/flutter/pull/178015) was merged, Android platform views experience gesture blocking after multi-touch operations (e.g., pinch-to-zoom). The issue occurs because: 1. PR #178015 changed framework behavior to encode original pointer count in events 2. Framework now sends events with **different actions** than the original MotionEvent - Example: Framework sends `ACTION_MOVE (2)` while original event is `ACTION_POINTER_UP (6)` 3. The existing engine code only checked **pointer count**, not action 4. This caused the engine to use original event with wrong action → gesture blocked ### Logs Showing the Issue trackedEvent.pointerCount=2, touch.pointerCount=2, trackedEvent.action=6 (ACTION_POINTER_UP), touch.action=2 (ACTION_MOVE) → Pointer counts MATCH, using original event → WebView receives ACTION_POINTER_UP but expects ACTION_MOVE → Gesture blocked! ❌
This commit is contained in:
parent
d50dfb9bf7
commit
017974987f
@ -739,25 +739,26 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
|
||||
.toArray(new PointerProperties[touch.pointerCount]);
|
||||
|
||||
if (!usingVirtualDiplay && trackedEvent != null) {
|
||||
// We have the original event. Check if pointer counts match.
|
||||
if (trackedEvent.getPointerCount() == touch.pointerCount) {
|
||||
// Pointer counts match - we can safely use the original event with offset.
|
||||
// We have the original event. Check if pointer counts and actions match.
|
||||
if (trackedEvent.getPointerCount() == touch.pointerCount
|
||||
&& trackedEvent.getAction() == touch.action) {
|
||||
// This preserves the verifiable input flag.
|
||||
translateMotionEvent(trackedEvent, pointerCoords);
|
||||
return trackedEvent;
|
||||
}
|
||||
|
||||
// Pointer count mismatch detected (e.g., gesture recognizer filtered some pointers).
|
||||
// Pointer count or action mismatch detected
|
||||
// (e.g., gesture recognizer filtered some pointers).
|
||||
// This commonly occurs when:
|
||||
// - Multi-touch gestures (zoom/pinch) are filtered by gesture recognizers
|
||||
//
|
||||
// We must reconstruct the event with the correct pointer count from Flutter.
|
||||
// We must reconstruct the event with the correct pointer count and action from Flutter.
|
||||
// Unfortunately, this loses Android's verifiable input flag because there is no
|
||||
// public API to modify pointer count while preserving verifiability.
|
||||
return MotionEvent.obtain(
|
||||
trackedEvent.getDownTime(),
|
||||
trackedEvent.getEventTime(),
|
||||
trackedEvent.getAction(),
|
||||
touch.action, // Use framework's action
|
||||
touch.pointerCount, // Use framework's pointer count
|
||||
pointerProperties,
|
||||
pointerCoords,
|
||||
|
||||
@ -407,8 +407,8 @@ public class PlatformViewsControllerTest {
|
||||
frameWorkTouch,
|
||||
false // usingVirtualDisplays
|
||||
);
|
||||
assertEquals(resolvedEvent.getAction(), original.getAction());
|
||||
assertNotEquals(resolvedEvent.getAction(), frameWorkTouch.action);
|
||||
assertEquals(frameWorkTouch.action, resolvedEvent.getAction());
|
||||
assertNotEquals(original.getAction(), resolvedEvent.getAction());
|
||||
}
|
||||
|
||||
private MotionEvent makePlatformViewTouchAndInvokeToMotionEvent(
|
||||
@ -752,6 +752,109 @@ public class PlatformViewsControllerTest {
|
||||
assertEquals(100.0f, resolvedEvent.getY(1), 0.001f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toMotionEvent_handlesActionMismatch() {
|
||||
// This test verifies the fix for action mismatch after PR #178015.
|
||||
// When framework sends different action than original (e.g., ACTION_MOVE instead of
|
||||
// ACTION_POINTER_UP during multi-touch), we must reconstruct the event.
|
||||
MotionEventTracker motionEventTracker = MotionEventTracker.getInstance();
|
||||
PlatformViewsController platformViewsController = new PlatformViewsController();
|
||||
|
||||
// Original multi-touch event with ACTION_POINTER_UP (action code 6)
|
||||
// This happens when second finger lifts during zoom gesture
|
||||
MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[2];
|
||||
properties[0] = new MotionEvent.PointerProperties();
|
||||
properties[0].id = 0;
|
||||
properties[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
|
||||
properties[1] = new MotionEvent.PointerProperties();
|
||||
properties[1].id = 1;
|
||||
properties[1].toolType = MotionEvent.TOOL_TYPE_FINGER;
|
||||
|
||||
MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[2];
|
||||
coords[0] = new MotionEvent.PointerCoords();
|
||||
coords[0].x = 100;
|
||||
coords[0].y = 100;
|
||||
coords[1] = new MotionEvent.PointerCoords();
|
||||
coords[1].x = 200;
|
||||
coords[1].y = 200;
|
||||
|
||||
MotionEvent original =
|
||||
MotionEvent.obtain(
|
||||
10, // downTime
|
||||
10, // eventTime
|
||||
MotionEvent.ACTION_POINTER_UP, // action = 6
|
||||
2, // pointerCount
|
||||
properties,
|
||||
coords,
|
||||
0, // metaState
|
||||
0, // buttonState
|
||||
1.0f, // xPrecision
|
||||
1.0f, // yPrecision
|
||||
0, // deviceId
|
||||
0, // edgeFlags
|
||||
0, // source
|
||||
0 // flags
|
||||
);
|
||||
|
||||
MotionEventTracker.MotionEventId motionEventId = motionEventTracker.track(original);
|
||||
|
||||
// After PR #178015, framework sends ACTION_MOVE (2) instead of ACTION_POINTER_UP (6)
|
||||
// Pointer count matches (2), but action is different
|
||||
List<List<Integer>> frameworkPointerProperties =
|
||||
Arrays.asList(
|
||||
Arrays.asList(0, MotionEvent.TOOL_TYPE_FINGER),
|
||||
Arrays.asList(1, MotionEvent.TOOL_TYPE_FINGER));
|
||||
|
||||
List<List<Double>> frameworkPointerCoords =
|
||||
Arrays.asList(
|
||||
Arrays.asList(0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 100.0, 100.0),
|
||||
Arrays.asList(0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 200.0, 200.0));
|
||||
|
||||
PlatformViewTouch touch =
|
||||
new PlatformViewTouch(
|
||||
0, // viewId
|
||||
original.getDownTime(),
|
||||
original.getEventTime(),
|
||||
MotionEvent.ACTION_MOVE, // Framework sends ACTION_MOVE (2)
|
||||
2, // pointerCount - matches original!
|
||||
frameworkPointerProperties,
|
||||
frameworkPointerCoords,
|
||||
original.getMetaState(),
|
||||
original.getButtonState(),
|
||||
original.getXPrecision(),
|
||||
original.getYPrecision(),
|
||||
original.getDeviceId(),
|
||||
original.getEdgeFlags(),
|
||||
original.getSource(),
|
||||
original.getFlags(),
|
||||
motionEventId.getId());
|
||||
|
||||
MotionEvent resolvedEvent =
|
||||
platformViewsController.toMotionEvent(
|
||||
1, // density
|
||||
touch,
|
||||
false // usingVirtualDisplays
|
||||
);
|
||||
|
||||
// Verify that resolved event uses framework's action (ACTION_MOVE)
|
||||
// not original (ACTION_POINTER_UP)
|
||||
assertEquals(MotionEvent.ACTION_MOVE, resolvedEvent.getAction());
|
||||
assertNotEquals(original.getAction(), resolvedEvent.getAction());
|
||||
|
||||
// Verify pointer count matches
|
||||
assertEquals(2, resolvedEvent.getPointerCount());
|
||||
|
||||
// Verify coordinates are correct
|
||||
assertEquals(100.0f, resolvedEvent.getX(0), 0.001f);
|
||||
assertEquals(100.0f, resolvedEvent.getY(0), 0.001f);
|
||||
assertEquals(200.0f, resolvedEvent.getX(1), 0.001f);
|
||||
assertEquals(200.0f, resolvedEvent.getY(1), 0.001f);
|
||||
|
||||
// Verify other properties preserved
|
||||
assertEquals(original.getDownTime(), resolvedEvent.getDownTime());
|
||||
assertEquals(original.getEventTime(), resolvedEvent.getEventTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
|
||||
public void getPlatformViewById_hybridComposition() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user