Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 499b626

Browse files
committed
Fix unexpected pointer change (#129765)
MotionEvent.isFromSource() is not mockable because it's from super class in InputEvent. So we need to change if condition from "if (!isPointerEvent || !isMovementEvent)" to "if (isPointerEvent || isMovementEvent)". Without this change, this test case will end up with "wanted but not invoked" error. Signed-off-by: xiaohu.hxh <[email protected]>
1 parent 39740fc commit 499b626

File tree

2 files changed

+91
-22
lines changed

2 files changed

+91
-22
lines changed

shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
import java.util.HashMap;
1414
import java.util.Map;
1515

16+
import io.flutter.Log;
17+
1618
/** Sends touch information from Android to Flutter in a format that Flutter understands. */
1719
public class AndroidTouchProcessor {
1820

21+
private static final String TAG = "AndroidTouchProcessor";
1922
// Must match the PointerChange enum in pointer.dart.
2023
@IntDef({
2124
PointerChange.CANCEL,
@@ -109,6 +112,7 @@ public AndroidTouchProcessor(@NonNull FlutterRenderer renderer, boolean trackMot
109112
}
110113

111114
public boolean onTouchEvent(@NonNull MotionEvent event) {
115+
Log.v(TAG, "onTouchEvent, event " + event);
112116
return onTouchEvent(event, IDENTITY_TRANSFORM);
113117
}
114118

@@ -186,29 +190,33 @@ public boolean onTouchEvent(@NonNull MotionEvent event, @NonNull Matrix transfor
186190
public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
187191
// Method isFromSource is only available in API 18+ (Jelly Bean MR2)
188192
// Mouse hover support is not implemented for API < 18.
193+
194+
Log.v(TAG, "onGenericMotionEvent, event " + event);
195+
189196
boolean isPointerEvent =
190197
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
191198
&& event.isFromSource(InputDevice.SOURCE_CLASS_POINTER);
199+
192200
boolean isMovementEvent =
193201
(event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE
194202
|| event.getActionMasked() == MotionEvent.ACTION_SCROLL);
195-
if (!isPointerEvent || !isMovementEvent) {
196-
return false;
197-
}
198-
199-
int pointerChange = getPointerChangeForAction(event.getActionMasked());
200-
ByteBuffer packet =
201-
ByteBuffer.allocateDirect(
202-
event.getPointerCount() * POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD);
203-
packet.order(ByteOrder.LITTLE_ENDIAN);
204203

205-
// ACTION_HOVER_MOVE always applies to a single pointer only.
206-
addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, IDENTITY_TRANSFORM, packet);
207-
if (packet.position() % (POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD) != 0) {
208-
throw new AssertionError("Packet position is not on field boundary.");
204+
if (isPointerEvent || isMovementEvent) {
205+
int pointerChange = getPointerChangeForAction(event.getActionMasked());
206+
ByteBuffer packet =
207+
ByteBuffer.allocateDirect(
208+
event.getPointerCount() * POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD);
209+
packet.order(ByteOrder.LITTLE_ENDIAN);
210+
211+
// ACTION_HOVER_MOVE always applies to a single pointer only.
212+
addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, IDENTITY_TRANSFORM, packet);
213+
if (packet.position() % (POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD) != 0) {
214+
throw new AssertionError("Packet position is not on field boundary.");
215+
}
216+
renderer.dispatchPointerDataPacket(packet, packet.position());
217+
return true;
209218
}
210-
renderer.dispatchPointerDataPacket(packet, packet.position());
211-
return true;
219+
return false;
212220
}
213221

214222
// TODO(mattcarroll): consider creating a PointerPacket class instead of using a procedure that
@@ -252,6 +260,7 @@ private void addPointerForIndex(
252260
buttons = 0;
253261
}
254262

263+
int panZoomType = -1;
255264
boolean isTrackpadPan = ongoingPans.containsKey(event.getPointerId(pointerIndex));
256265

257266
int signalKind =
@@ -264,8 +273,14 @@ private void addPointerForIndex(
264273
packet.putLong(motionEventId); // motionEventId
265274
packet.putLong(timeStamp); // time_stamp
266275
if (isTrackpadPan) {
267-
packet.putLong(getPointerChangeForPanZoom(pointerChange)); // change
268-
packet.putLong(PointerDeviceKind.TRACKPAD); // kind
276+
panZoomType = getPointerChangeForPanZoom(pointerChange);
277+
if (panZoomType > 0) {
278+
packet.putLong(panZoomType); // change
279+
packet.putLong(PointerDeviceKind.TRACKPAD); // kind
280+
} else {
281+
packet.putLong(pointerChange); // change
282+
packet.putLong(pointerKind); // kind
283+
}
269284
} else {
270285
packet.putLong(pointerChange); // change
271286
packet.putLong(pointerKind); // kind
@@ -274,7 +289,7 @@ private void addPointerForIndex(
274289
packet.putLong(event.getPointerId(pointerIndex)); // device
275290
packet.putLong(0); // pointer_identifier, will be generated in pointer_data_packet_converter.cc.
276291

277-
if (isTrackpadPan) {
292+
if (isTrackpadPan && (panZoomType > 0)) {
278293
float[] panStart = ongoingPans.get(event.getPointerId(pointerIndex));
279294
packet.putDouble(panStart[0]);
280295
packet.putDouble(panStart[1]);
@@ -342,7 +357,7 @@ private void addPointerForIndex(
342357
packet.putDouble(0.0); // scroll_delta_x
343358
}
344359

345-
if (isTrackpadPan) {
360+
if (isTrackpadPan && (panZoomType > 0)) {
346361
float[] panStart = ongoingPans.get(event.getPointerId(pointerIndex));
347362
packet.putDouble(viewToScreenCoords[0] - panStart[0]);
348363
packet.putDouble(viewToScreenCoords[1] - panStart[1]);
@@ -355,7 +370,7 @@ private void addPointerForIndex(
355370
packet.putDouble(1.0); // scale
356371
packet.putDouble(0.0); // rotation
357372

358-
if (isTrackpadPan && getPointerChangeForPanZoom(pointerChange) == PointerChange.PAN_ZOOM_END) {
373+
if (isTrackpadPan && (panZoomType == PointerChange.PAN_ZOOM_END)) {
359374
ongoingPans.remove(event.getPointerId(pointerIndex));
360375
}
361376
}
@@ -396,12 +411,12 @@ private int getPointerChangeForAction(int maskedAction) {
396411
private int getPointerChangeForPanZoom(int pointerChange) {
397412
if (pointerChange == PointerChange.DOWN) {
398413
return PointerChange.PAN_ZOOM_START;
399-
} else if (pointerChange == PointerChange.MOVE) {
414+
} else if (pointerChange == PointerChange.MOVE || pointerChange == PointerChange.HOVER) {
400415
return PointerChange.PAN_ZOOM_UPDATE;
401416
} else if (pointerChange == PointerChange.UP || pointerChange == PointerChange.CANCEL) {
402417
return PointerChange.PAN_ZOOM_END;
403418
}
404-
throw new AssertionError("Unexpected pointer change");
419+
return -1;
405420
}
406421

407422
@PointerDeviceKind

shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,58 @@ public void unexpectedMaskedAction() {
218218
touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_BUTTON_PRESS, 0.0f, 0.0f, 0));
219219
verify(mockRenderer, never()).dispatchPointerDataPacket(ByteBuffer.allocate(0), 0);
220220
}
221+
222+
@Test
223+
public void unexpectedPointerChange() {
224+
// Regression test for https://github.com/flutter/flutter/issues/129765
225+
226+
MotionEventMocker mocker =
227+
new MotionEventMocker(0, InputDevice.SOURCE_MOUSE, MotionEvent.TOOL_TYPE_MOUSE);
228+
229+
touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_DOWN, 0.0f, 0.0f, 0));
230+
InOrder inOrder = inOrder(mockRenderer);
231+
inOrder
232+
.verify(mockRenderer)
233+
.dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture());
234+
ByteBuffer packet = packetCaptor.getValue();
235+
assertEquals(AndroidTouchProcessor.PointerChange.PAN_ZOOM_START, readPointerChange(packet));
236+
assertEquals(AndroidTouchProcessor.PointerDeviceKind.TRACKPAD, readPointerDeviceKind(packet));
237+
assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet));
238+
assertEquals(0.0, readPointerPhysicalX(packet));
239+
assertEquals(0.0, readPointerPhysicalY(packet));
240+
241+
touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_MOVE, 10.0f, 5.0f, 0));
242+
inOrder
243+
.verify(mockRenderer)
244+
.dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture());
245+
packet = packetCaptor.getValue();
246+
assertEquals(AndroidTouchProcessor.PointerChange.PAN_ZOOM_UPDATE, readPointerChange(packet));
247+
assertEquals(AndroidTouchProcessor.PointerDeviceKind.TRACKPAD, readPointerDeviceKind(packet));
248+
assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet));
249+
assertEquals(0.0, readPointerPhysicalX(packet));
250+
assertEquals(0.0, readPointerPhysicalY(packet));
251+
assertEquals(10.0, readPointerPanX(packet));
252+
assertEquals(5.0, readPointerPanY(packet));
253+
254+
touchProcessor.onGenericMotionEvent(mocker.mockEvent(MotionEvent.ACTION_SCROLL, 0.0f, 0.0f, 0));
255+
inOrder
256+
.verify(mockRenderer)
257+
.dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture());
258+
packet = packetCaptor.getValue();
259+
assertEquals(0.0, readPointerPhysicalX(packet));
260+
assertEquals(0.0, readPointerPhysicalY(packet));
261+
262+
touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_UP, 10.0f, 5.0f, 0));
263+
inOrder
264+
.verify(mockRenderer)
265+
.dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture());
266+
packet = packetCaptor.getValue();
267+
assertEquals(AndroidTouchProcessor.PointerChange.PAN_ZOOM_END, readPointerChange(packet));
268+
assertEquals(AndroidTouchProcessor.PointerDeviceKind.TRACKPAD, readPointerDeviceKind(packet));
269+
assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet));
270+
assertEquals(0.0, readPointerPhysicalX(packet));
271+
assertEquals(0.0, readPointerPhysicalY(packet));
272+
inOrder.verifyNoMoreInteractions();
273+
}
274+
221275
}

0 commit comments

Comments
 (0)