From 6a21ca9c586a380d85d3dd9fa6243eda57597611 Mon Sep 17 00:00:00 2001 From: Callum Moffat Date: Fri, 11 Feb 2022 21:34:59 -0500 Subject: [PATCH 1/6] Android trackpad gestures --- .../android/AndroidTouchProcessor.java | 86 +++++++++++++------ 1 file changed, 61 insertions(+), 25 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index 2078bdc161e9f..9ed332d0eff09 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -9,6 +9,8 @@ import io.flutter.embedding.engine.renderer.FlutterRenderer; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.Map; /** Sends touch information from Android to Flutter in a format that Flutter understands. */ public class AndroidTouchProcessor { @@ -82,6 +84,8 @@ public class AndroidTouchProcessor { private final boolean trackMotionEvents; + private final Map ongoingPans = new HashMap<>(); + /** * Constructs an {@code AndroidTouchProcessor} that will send touch event data to the Flutter * execution context represented by the given {@link FlutterRenderer}. @@ -220,6 +224,23 @@ private void addPointerForIndex( } int pointerKind = getPointerDeviceTypeForToolType(event.getToolType(pointerIndex)); + // We use this in lieu of using event.getRawX and event.getRawY as we wish to support + // earlier versions than API level 29. + float viewToScreenCoords[] = {event.getX(pointerIndex), event.getY(pointerIndex)}; + transformMatrix.mapPoints(viewToScreenCoords); + long buttons; + if (pointerKind == PointerDeviceKind.MOUSE) { + buttons = event.getButtonState() & 0x1F; + if (buttons == 0 + && event.getSource() == InputDevice.SOURCE_MOUSE + && pointerChange == PointerChange.DOWN) { + ongoingPans.put(event.getPointerId(pointerIndex), viewToScreenCoords); + } + } else if (pointerKind == PointerDeviceKind.STYLUS) { + buttons = (event.getButtonState() >> 4) & 0xF; + } else { + buttons = 0; + } int signalKind = event.getActionMasked() == MotionEvent.ACTION_SCROLL @@ -230,39 +251,31 @@ private void addPointerForIndex( packet.putLong(motionEventId); // motionEventId packet.putLong(timeStamp); // time_stamp - packet.putLong(pointerChange); // change - packet.putLong(pointerKind); // kind + if (ongoingPans.containsKey(event.getPointerId(pointerIndex))) { + packet.putLong(getPointerChangeForPanZoom(pointerChange)); // change + packet.putLong(PointerDeviceKind.TRACKPAD); // kind + } else { + packet.putLong(pointerChange); // change + packet.putLong(pointerKind); // kind + } packet.putLong(signalKind); // signal_kind packet.putLong(event.getPointerId(pointerIndex)); // device packet.putLong(0); // pointer_identifier, will be generated in pointer_data_packet_converter.cc. - // We use this in lieu of using event.getRawX and event.getRawY as we wish to support - // earlier versions than API level 29. - float viewToScreenCoords[] = {event.getX(pointerIndex), event.getY(pointerIndex)}; - transformMatrix.mapPoints(viewToScreenCoords); - packet.putDouble(viewToScreenCoords[0]); // physical_x - packet.putDouble(viewToScreenCoords[1]); // physical_y + if (ongoingPans.containsKey(event.getPointerId(pointerIndex))) { + float[] panStart = ongoingPans.get(event.getPointerId(pointerIndex)); + packet.putDouble(panStart[0]); + packet.putDouble(panStart[1]); + } else { + packet.putDouble(viewToScreenCoords[0]); // physical_x + packet.putDouble(viewToScreenCoords[1]); // physical_y + } packet.putDouble( 0.0); // physical_delta_x, will be generated in pointer_data_packet_converter.cc. packet.putDouble( 0.0); // physical_delta_y, will be generated in pointer_data_packet_converter.cc. - long buttons; - if (pointerKind == PointerDeviceKind.MOUSE) { - buttons = event.getButtonState() & 0x1F; - // TODO(dkwingsmt): Remove this fix after implementing touchpad gestures - // https://github.com/flutter/flutter/issues/23604#issuecomment-524471152 - if (buttons == 0 - && event.getSource() == InputDevice.SOURCE_MOUSE - && (pointerChange == PointerChange.DOWN || pointerChange == PointerChange.MOVE)) { - buttons = _POINTER_BUTTON_PRIMARY; - } - } else if (pointerKind == PointerDeviceKind.STYLUS) { - buttons = (event.getButtonState() >> 4) & 0xF; - } else { - buttons = 0; - } packet.putLong(buttons); // buttons packet.putLong(0); // obscured @@ -317,12 +330,23 @@ private void addPointerForIndex( packet.putDouble(0.0); // scroll_delta_x } - packet.putDouble(0.0); // pan_x - packet.putDouble(0.0); // pan_y + if (ongoingPans.containsKey(event.getPointerId(pointerIndex))) { + float[] panStart = ongoingPans.get(event.getPointerId(pointerIndex)); + packet.putDouble(viewToScreenCoords[0] - panStart[0]); + packet.putDouble(viewToScreenCoords[1] - panStart[1]); + } else { + packet.putDouble(0.0); // pan_x + packet.putDouble(0.0); // pan_y + } packet.putDouble(0.0); // pan_delta_x packet.putDouble(0.0); // pan_delta_y packet.putDouble(1.0); // scale packet.putDouble(0.0); // rotation + + if (ongoingPans.containsKey(event.getPointerId(pointerIndex)) + && getPointerChangeForPanZoom(pointerChange) == PointerChange.PAN_ZOOM_END) { + ongoingPans.remove(event.getPointerId(pointerIndex)); + } } @PointerChange @@ -357,6 +381,18 @@ private int getPointerChangeForAction(int maskedAction) { return -1; } + @PointerChange + private int getPointerChangeForPanZoom(int pointerChange) { + if (pointerChange == PointerChange.DOWN) { + return PointerChange.PAN_ZOOM_START; + } else if (pointerChange == PointerChange.MOVE) { + return PointerChange.PAN_ZOOM_UPDATE; + } else if (pointerChange == PointerChange.UP || pointerChange == PointerChange.CANCEL) { + return PointerChange.PAN_ZOOM_END; + } + return -1; + } + @PointerDeviceKind private int getPointerDeviceTypeForToolType(int toolType) { switch (toolType) { From 5120130454eb0f8a4220e3ee4147994bcd57f8b6 Mon Sep 17 00:00:00 2001 From: Callum Moffat Date: Thu, 31 Mar 2022 18:41:48 -0400 Subject: [PATCH 2/6] Add test --- .../android/AndroidTouchProcessor.java | 8 +- .../android/AndroidTouchProcessorTest.java | 252 ++++++++++++++++++ 2 files changed, 256 insertions(+), 4 deletions(-) create mode 100644 shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index 9ed332d0eff09..b0d2e28885356 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -28,7 +28,7 @@ public class AndroidTouchProcessor { PointerChange.PAN_ZOOM_UPDATE, PointerChange.PAN_ZOOM_END }) - private @interface PointerChange { + public @interface PointerChange { int CANCEL = 0; int ADD = 1; int REMOVE = 2; @@ -50,7 +50,7 @@ public class AndroidTouchProcessor { PointerDeviceKind.TRACKPAD, PointerDeviceKind.UNKNOWN }) - private @interface PointerDeviceKind { + public @interface PointerDeviceKind { int TOUCH = 0; int MOUSE = 1; int STYLUS = 2; @@ -61,7 +61,7 @@ public class AndroidTouchProcessor { // Must match the PointerSignalKind enum in pointer.dart. @IntDef({PointerSignalKind.NONE, PointerSignalKind.SCROLL, PointerSignalKind.UNKNOWN}) - private @interface PointerSignalKind { + public @interface PointerSignalKind { int NONE = 0; int SCROLL = 1; int UNKNOWN = 2; @@ -69,7 +69,7 @@ public class AndroidTouchProcessor { // Must match the unpacking code in hooks.dart. private static final int POINTER_DATA_FIELD_COUNT = 35; - private static final int BYTES_PER_FIELD = 8; + public static final int BYTES_PER_FIELD = 8; // This value must match the value in framework's platform_view.dart. // This flag indicates whether the original Android pointer events were batched together. diff --git a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java new file mode 100644 index 0000000000000..78a2b35e6d35c --- /dev/null +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -0,0 +1,252 @@ +package io.flutter.embedding.android; + +import static android.view.MotionEvent.PointerCoords; +import static android.view.MotionEvent.PointerProperties; +import static junit.framework.TestCase.assertEquals; +import static org.mockito.Mockito.inOrder; + +import android.annotation.TargetApi; +import android.view.InputDevice; +import android.view.MotionEvent; +import androidx.test.core.view.MotionEventBuilder; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import io.flutter.embedding.engine.renderer.FlutterRenderer; +import java.nio.ByteBuffer; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +@Config(manifest = Config.NONE) +@RunWith(AndroidJUnit4.class) +@TargetApi(28) +public class AndroidTouchProcessorTest { + @Mock FlutterRenderer mockRenderer; + AndroidTouchProcessor touchProcessor; + @Captor ArgumentCaptor packetCaptor; + @Captor ArgumentCaptor packetSizeCaptor; + + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + touchProcessor = new AndroidTouchProcessor(mockRenderer, false); + } + + private long readPointerChange(ByteBuffer buffer) { + return buffer.getLong(2 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private long readPointerDeviceKind(ByteBuffer buffer) { + return buffer.getLong(3 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private long readPointerSignalKind(ByteBuffer buffer) { + return buffer.getLong(4 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readPointerPhysicalX(ByteBuffer buffer) { + return buffer.getDouble(7 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readPointerPhysicalY(ByteBuffer buffer) { + return buffer.getDouble(8 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readPointerPanX(ByteBuffer buffer) { + return buffer.getDouble(29 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readPointerPanY(ByteBuffer buffer) { + return buffer.getDouble(30 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + @Test + public void normalTouch() { + PointerProperties properties = new PointerProperties(); + properties.id = 0; + properties.toolType = MotionEvent.TOOL_TYPE_FINGER; + PointerCoords coordinates = new PointerCoords(); + touchProcessor.onTouchEvent( + MotionEventBuilder.newBuilder() + .setAction(MotionEvent.ACTION_DOWN) + .setActionIndex(0) + .setDeviceId(1) + .setPointer(properties, coordinates) + .build()); + InOrder inOrder = inOrder(mockRenderer); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + ByteBuffer packet = packetCaptor.getValue(); + assertEquals(AndroidTouchProcessor.PointerChange.DOWN, readPointerChange(packet)); + assertEquals(AndroidTouchProcessor.PointerDeviceKind.TOUCH, readPointerDeviceKind(packet)); + assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet)); + assertEquals(0.0, readPointerPhysicalX(packet)); + assertEquals(0.0, readPointerPhysicalY(packet)); + coordinates.x = 10; + coordinates.y = 5; + touchProcessor.onTouchEvent( + MotionEventBuilder.newBuilder() + .setAction(MotionEvent.ACTION_MOVE) + .setActionIndex(0) + .setDeviceId(1) + .setPointer(properties, coordinates) + .build()); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + packet = packetCaptor.getValue(); + assertEquals(AndroidTouchProcessor.PointerChange.MOVE, readPointerChange(packet)); + assertEquals(AndroidTouchProcessor.PointerDeviceKind.TOUCH, readPointerDeviceKind(packet)); + assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet)); + assertEquals(10.0, readPointerPhysicalX(packet)); + assertEquals(5.0, readPointerPhysicalY(packet)); + touchProcessor.onTouchEvent( + MotionEventBuilder.newBuilder() + .setAction(MotionEvent.ACTION_UP) + .setActionIndex(0) + .setDeviceId(1) + .setPointer(properties, coordinates) + .build()); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + packet = packetCaptor.getValue(); + assertEquals(AndroidTouchProcessor.PointerChange.UP, readPointerChange(packet)); + assertEquals(AndroidTouchProcessor.PointerDeviceKind.TOUCH, readPointerDeviceKind(packet)); + assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet)); + assertEquals(10.0, readPointerPhysicalX(packet)); + assertEquals(5.0, readPointerPhysicalY(packet)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void trackpadGesture() { + PointerProperties properties = new PointerProperties(); + properties.id = 0; + properties.toolType = MotionEvent.TOOL_TYPE_MOUSE; + PointerCoords coordinates = new PointerCoords(); + touchProcessor.onTouchEvent( + MotionEventBuilder.newBuilder() + .setAction(MotionEvent.ACTION_DOWN) + .setActionIndex(0) + .setDeviceId(1) + .setPointer(properties, coordinates) + .setSource(InputDevice.SOURCE_MOUSE) + .build()); + InOrder inOrder = inOrder(mockRenderer); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + ByteBuffer packet = packetCaptor.getValue(); + assertEquals(AndroidTouchProcessor.PointerChange.PAN_ZOOM_START, readPointerChange(packet)); + assertEquals(AndroidTouchProcessor.PointerDeviceKind.TRACKPAD, readPointerDeviceKind(packet)); + assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet)); + assertEquals(0.0, readPointerPhysicalX(packet)); + assertEquals(0.0, readPointerPhysicalY(packet)); + coordinates.x = 10; + coordinates.y = 5; + touchProcessor.onTouchEvent( + MotionEventBuilder.newBuilder() + .setAction(MotionEvent.ACTION_MOVE) + .setActionIndex(0) + .setDeviceId(1) + .setPointer(properties, coordinates) + .setSource(InputDevice.SOURCE_MOUSE) + .build()); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + packet = packetCaptor.getValue(); + assertEquals(AndroidTouchProcessor.PointerChange.PAN_ZOOM_UPDATE, readPointerChange(packet)); + assertEquals(AndroidTouchProcessor.PointerDeviceKind.TRACKPAD, readPointerDeviceKind(packet)); + assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet)); + assertEquals(0.0, readPointerPhysicalX(packet)); + assertEquals(0.0, readPointerPhysicalY(packet)); + assertEquals(10.0, readPointerPanX(packet)); + assertEquals(5.0, readPointerPanY(packet)); + touchProcessor.onTouchEvent( + MotionEventBuilder.newBuilder() + .setAction(MotionEvent.ACTION_UP) + .setActionIndex(0) + .setDeviceId(1) + .setPointer(properties, coordinates) + .setSource(InputDevice.SOURCE_MOUSE) + .build()); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + packet = packetCaptor.getValue(); + assertEquals(AndroidTouchProcessor.PointerChange.PAN_ZOOM_END, readPointerChange(packet)); + assertEquals(AndroidTouchProcessor.PointerDeviceKind.TRACKPAD, readPointerDeviceKind(packet)); + assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet)); + assertEquals(0.0, readPointerPhysicalX(packet)); + assertEquals(0.0, readPointerPhysicalY(packet)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void mouse() { + PointerProperties properties = new PointerProperties(); + properties.id = 0; + properties.toolType = MotionEvent.TOOL_TYPE_MOUSE; + PointerCoords coordinates = new PointerCoords(); + touchProcessor.onTouchEvent( + MotionEventBuilder.newBuilder() + .setAction(MotionEvent.ACTION_DOWN) + .setActionIndex(0) + .setDeviceId(1) + .setPointer(properties, coordinates) + .build()); + InOrder inOrder = inOrder(mockRenderer); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + ByteBuffer packet = packetCaptor.getValue(); + assertEquals(AndroidTouchProcessor.PointerChange.DOWN, readPointerChange(packet)); + assertEquals(AndroidTouchProcessor.PointerDeviceKind.MOUSE, readPointerDeviceKind(packet)); + assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet)); + assertEquals(0.0, readPointerPhysicalX(packet)); + assertEquals(0.0, readPointerPhysicalY(packet)); + coordinates.x = 10; + coordinates.y = 5; + touchProcessor.onTouchEvent( + MotionEventBuilder.newBuilder() + .setAction(MotionEvent.ACTION_MOVE) + .setActionIndex(0) + .setDeviceId(1) + .setPointer(properties, coordinates) + .build()); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + packet = packetCaptor.getValue(); + assertEquals(AndroidTouchProcessor.PointerChange.MOVE, readPointerChange(packet)); + assertEquals(AndroidTouchProcessor.PointerDeviceKind.MOUSE, readPointerDeviceKind(packet)); + assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet)); + assertEquals(10.0, readPointerPhysicalX(packet)); + assertEquals(5.0, readPointerPhysicalY(packet)); + touchProcessor.onTouchEvent( + MotionEventBuilder.newBuilder() + .setAction(MotionEvent.ACTION_UP) + .setActionIndex(0) + .setDeviceId(1) + .setPointer(properties, coordinates) + .build()); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + packet = packetCaptor.getValue(); + assertEquals(AndroidTouchProcessor.PointerChange.UP, readPointerChange(packet)); + assertEquals(AndroidTouchProcessor.PointerDeviceKind.MOUSE, readPointerDeviceKind(packet)); + assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet)); + assertEquals(10.0, readPointerPhysicalX(packet)); + assertEquals(5.0, readPointerPhysicalY(packet)); + inOrder.verifyNoMoreInteractions(); + } +} From b41a92b56ca38a5952ca8e72fe8c3da9755fc5ac Mon Sep 17 00:00:00 2001 From: Callum Moffat Date: Tue, 5 Apr 2022 19:59:32 -0400 Subject: [PATCH 3/6] Use mock instead of shadow --- .../android/AndroidTouchProcessorTest.java | 131 ++++++------------ 1 file changed, 44 insertions(+), 87 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java index 78a2b35e6d35c..ae2e31366e986 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -1,14 +1,13 @@ package io.flutter.embedding.android; -import static android.view.MotionEvent.PointerCoords; -import static android.view.MotionEvent.PointerProperties; import static junit.framework.TestCase.assertEquals; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.annotation.TargetApi; import android.view.InputDevice; import android.view.MotionEvent; -import androidx.test.core.view.MotionEventBuilder; import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.engine.renderer.FlutterRenderer; import java.nio.ByteBuffer; @@ -65,19 +64,38 @@ private double readPointerPanY(ByteBuffer buffer) { return buffer.getDouble(30 * AndroidTouchProcessor.BYTES_PER_FIELD); } + private class MotionEventMocker { + int pointerId; + int source; + int toolType; + + MotionEventMocker(int pointerId, int source, int toolType) { + this.pointerId = pointerId; + this.source = source; + this.toolType = toolType; + } + + MotionEvent mockEvent(int action, float x, float y, int buttonState) { + MotionEvent event = mock(MotionEvent.class); + when(event.getDevice()).thenReturn(null); + when(event.getSource()).thenReturn(source); + when(event.getPointerCount()).thenReturn(1); + when(event.getActionMasked()).thenReturn(action); + when(event.getActionIndex()).thenReturn(0); + when(event.getButtonState()).thenReturn(buttonState); + when(event.getPointerId(0)).thenReturn(pointerId); + when(event.getX(0)).thenReturn(x); + when(event.getY(0)).thenReturn(y); + when(event.getToolType(0)).thenReturn(toolType); + return event; + } + } + @Test public void normalTouch() { - PointerProperties properties = new PointerProperties(); - properties.id = 0; - properties.toolType = MotionEvent.TOOL_TYPE_FINGER; - PointerCoords coordinates = new PointerCoords(); - touchProcessor.onTouchEvent( - MotionEventBuilder.newBuilder() - .setAction(MotionEvent.ACTION_DOWN) - .setActionIndex(0) - .setDeviceId(1) - .setPointer(properties, coordinates) - .build()); + MotionEventMocker mocker = + new MotionEventMocker(0, InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.TOOL_TYPE_FINGER); + touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_DOWN, 0.0f, 0.0f, 0)); InOrder inOrder = inOrder(mockRenderer); inOrder .verify(mockRenderer) @@ -88,15 +106,7 @@ public void normalTouch() { assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet)); assertEquals(0.0, readPointerPhysicalX(packet)); assertEquals(0.0, readPointerPhysicalY(packet)); - coordinates.x = 10; - coordinates.y = 5; - touchProcessor.onTouchEvent( - MotionEventBuilder.newBuilder() - .setAction(MotionEvent.ACTION_MOVE) - .setActionIndex(0) - .setDeviceId(1) - .setPointer(properties, coordinates) - .build()); + touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_MOVE, 10.0f, 5.0f, 0)); inOrder .verify(mockRenderer) .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); @@ -106,13 +116,7 @@ public void normalTouch() { assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet)); assertEquals(10.0, readPointerPhysicalX(packet)); assertEquals(5.0, readPointerPhysicalY(packet)); - touchProcessor.onTouchEvent( - MotionEventBuilder.newBuilder() - .setAction(MotionEvent.ACTION_UP) - .setActionIndex(0) - .setDeviceId(1) - .setPointer(properties, coordinates) - .build()); + touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_UP, 10.0f, 5.0f, 0)); inOrder .verify(mockRenderer) .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); @@ -127,18 +131,9 @@ public void normalTouch() { @Test public void trackpadGesture() { - PointerProperties properties = new PointerProperties(); - properties.id = 0; - properties.toolType = MotionEvent.TOOL_TYPE_MOUSE; - PointerCoords coordinates = new PointerCoords(); - touchProcessor.onTouchEvent( - MotionEventBuilder.newBuilder() - .setAction(MotionEvent.ACTION_DOWN) - .setActionIndex(0) - .setDeviceId(1) - .setPointer(properties, coordinates) - .setSource(InputDevice.SOURCE_MOUSE) - .build()); + MotionEventMocker mocker = + new MotionEventMocker(1, InputDevice.SOURCE_MOUSE, MotionEvent.TOOL_TYPE_MOUSE); + touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_DOWN, 0.0f, 0.0f, 0)); InOrder inOrder = inOrder(mockRenderer); inOrder .verify(mockRenderer) @@ -149,16 +144,7 @@ public void trackpadGesture() { assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet)); assertEquals(0.0, readPointerPhysicalX(packet)); assertEquals(0.0, readPointerPhysicalY(packet)); - coordinates.x = 10; - coordinates.y = 5; - touchProcessor.onTouchEvent( - MotionEventBuilder.newBuilder() - .setAction(MotionEvent.ACTION_MOVE) - .setActionIndex(0) - .setDeviceId(1) - .setPointer(properties, coordinates) - .setSource(InputDevice.SOURCE_MOUSE) - .build()); + touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_MOVE, 10.0f, 5.0f, 0)); inOrder .verify(mockRenderer) .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); @@ -170,14 +156,7 @@ public void trackpadGesture() { assertEquals(0.0, readPointerPhysicalY(packet)); assertEquals(10.0, readPointerPanX(packet)); assertEquals(5.0, readPointerPanY(packet)); - touchProcessor.onTouchEvent( - MotionEventBuilder.newBuilder() - .setAction(MotionEvent.ACTION_UP) - .setActionIndex(0) - .setDeviceId(1) - .setPointer(properties, coordinates) - .setSource(InputDevice.SOURCE_MOUSE) - .build()); + touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_UP, 10.0f, 5.0f, 0)); inOrder .verify(mockRenderer) .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); @@ -192,17 +171,9 @@ public void trackpadGesture() { @Test public void mouse() { - PointerProperties properties = new PointerProperties(); - properties.id = 0; - properties.toolType = MotionEvent.TOOL_TYPE_MOUSE; - PointerCoords coordinates = new PointerCoords(); - touchProcessor.onTouchEvent( - MotionEventBuilder.newBuilder() - .setAction(MotionEvent.ACTION_DOWN) - .setActionIndex(0) - .setDeviceId(1) - .setPointer(properties, coordinates) - .build()); + MotionEventMocker mocker = + new MotionEventMocker(2, InputDevice.SOURCE_MOUSE, MotionEvent.TOOL_TYPE_MOUSE); + touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_DOWN, 0.0f, 0.0f, 1)); InOrder inOrder = inOrder(mockRenderer); inOrder .verify(mockRenderer) @@ -213,15 +184,7 @@ public void mouse() { assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet)); assertEquals(0.0, readPointerPhysicalX(packet)); assertEquals(0.0, readPointerPhysicalY(packet)); - coordinates.x = 10; - coordinates.y = 5; - touchProcessor.onTouchEvent( - MotionEventBuilder.newBuilder() - .setAction(MotionEvent.ACTION_MOVE) - .setActionIndex(0) - .setDeviceId(1) - .setPointer(properties, coordinates) - .build()); + touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_MOVE, 10.0f, 5.0f, 1)); inOrder .verify(mockRenderer) .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); @@ -231,13 +194,7 @@ public void mouse() { assertEquals(AndroidTouchProcessor.PointerSignalKind.NONE, readPointerSignalKind(packet)); assertEquals(10.0, readPointerPhysicalX(packet)); assertEquals(5.0, readPointerPhysicalY(packet)); - touchProcessor.onTouchEvent( - MotionEventBuilder.newBuilder() - .setAction(MotionEvent.ACTION_UP) - .setActionIndex(0) - .setDeviceId(1) - .setPointer(properties, coordinates) - .build()); + touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_UP, 10.0f, 5.0f, 1)); inOrder .verify(mockRenderer) .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); From eff543c92925be3fe2f964cd4672d2ace2b99646 Mon Sep 17 00:00:00 2001 From: Callum Moffat Date: Wed, 20 Apr 2022 01:25:53 -0400 Subject: [PATCH 4/6] Address feedback --- .../embedding/android/AndroidTouchProcessor.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index b0d2e28885356..34a49626aa9c7 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -242,6 +242,8 @@ private void addPointerForIndex( buttons = 0; } + boolean isTrackpadPan = ongoingPans.containsKey(event.getPointerId(pointerIndex)); + int signalKind = event.getActionMasked() == MotionEvent.ACTION_SCROLL ? PointerSignalKind.SCROLL @@ -251,7 +253,7 @@ private void addPointerForIndex( packet.putLong(motionEventId); // motionEventId packet.putLong(timeStamp); // time_stamp - if (ongoingPans.containsKey(event.getPointerId(pointerIndex))) { + if (isTrackpadPan) { packet.putLong(getPointerChangeForPanZoom(pointerChange)); // change packet.putLong(PointerDeviceKind.TRACKPAD); // kind } else { @@ -262,7 +264,7 @@ private void addPointerForIndex( packet.putLong(event.getPointerId(pointerIndex)); // device packet.putLong(0); // pointer_identifier, will be generated in pointer_data_packet_converter.cc. - if (ongoingPans.containsKey(event.getPointerId(pointerIndex))) { + if (isTrackpadPan) { float[] panStart = ongoingPans.get(event.getPointerId(pointerIndex)); packet.putDouble(panStart[0]); packet.putDouble(panStart[1]); @@ -330,7 +332,7 @@ private void addPointerForIndex( packet.putDouble(0.0); // scroll_delta_x } - if (ongoingPans.containsKey(event.getPointerId(pointerIndex))) { + if (isTrackpadPan) { float[] panStart = ongoingPans.get(event.getPointerId(pointerIndex)); packet.putDouble(viewToScreenCoords[0] - panStart[0]); packet.putDouble(viewToScreenCoords[1] - panStart[1]); @@ -343,8 +345,7 @@ private void addPointerForIndex( packet.putDouble(1.0); // scale packet.putDouble(0.0); // rotation - if (ongoingPans.containsKey(event.getPointerId(pointerIndex)) - && getPointerChangeForPanZoom(pointerChange) == PointerChange.PAN_ZOOM_END) { + if (isTrackpadPan && getPointerChangeForPanZoom(pointerChange) == PointerChange.PAN_ZOOM_END) { ongoingPans.remove(event.getPointerId(pointerIndex)); } } From c7ee3db5be3dc0cc4798809c5f598b0735b62dde Mon Sep 17 00:00:00 2001 From: Callum Moffat Date: Wed, 11 May 2022 14:57:31 -0400 Subject: [PATCH 5/6] Add comment --- .../io/flutter/embedding/android/AndroidTouchProcessor.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index 34a49626aa9c7..adad83ff44aee 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -234,6 +234,9 @@ private void addPointerForIndex( if (buttons == 0 && event.getSource() == InputDevice.SOURCE_MOUSE && pointerChange == PointerChange.DOWN) { + // Some implementations translate trackpad scrolling into a mouse down-move-up event + // sequence with buttons: 0, such as ARC on a Chromebook. See #11420, a legacy + // implementation that uses the same condition but converts differently. ongoingPans.put(event.getPointerId(pointerIndex), viewToScreenCoords); } } else if (pointerKind == PointerDeviceKind.STYLUS) { From 8df97aae8e02d063f40a9fe2076cdf6f8e5d05fa Mon Sep 17 00:00:00 2001 From: Callum Moffat Date: Thu, 2 Jun 2022 18:22:44 -0400 Subject: [PATCH 6/6] Feedback 2 --- .../io/flutter/embedding/android/AndroidTouchProcessor.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index adad83ff44aee..85cc1b71f30b0 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -6,6 +6,7 @@ import android.view.MotionEvent; import androidx.annotation.IntDef; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import io.flutter.embedding.engine.renderer.FlutterRenderer; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -69,7 +70,7 @@ public class AndroidTouchProcessor { // Must match the unpacking code in hooks.dart. private static final int POINTER_DATA_FIELD_COUNT = 35; - public static final int BYTES_PER_FIELD = 8; + @VisibleForTesting static final int BYTES_PER_FIELD = 8; // This value must match the value in framework's platform_view.dart. // This flag indicates whether the original Android pointer events were batched together. @@ -78,8 +79,6 @@ public class AndroidTouchProcessor { @NonNull private final FlutterRenderer renderer; @NonNull private final MotionEventTracker motionEventTracker; - private static final int _POINTER_BUTTON_PRIMARY = 1; - private static final Matrix IDENTITY_TRANSFORM = new Matrix(); private final boolean trackMotionEvents;