-
Notifications
You must be signed in to change notification settings - Fork 6k
ChromeOS/Android trackpad gestures #31595
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6,9 +6,12 @@ | |||||||||||||||||||||||||||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.HashMap; | ||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /** Sends touch information from Android to Flutter in a format that Flutter understands. */ | ||||||||||||||||||||||||||||||||||||||||||||||
| public class AndroidTouchProcessor { | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -26,7 +29,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; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -48,7 +51,7 @@ public class AndroidTouchProcessor { | |||||||||||||||||||||||||||||||||||||||||||||
| PointerDeviceKind.TRACKPAD, | ||||||||||||||||||||||||||||||||||||||||||||||
| PointerDeviceKind.UNKNOWN | ||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||
| private @interface PointerDeviceKind { | ||||||||||||||||||||||||||||||||||||||||||||||
| public @interface PointerDeviceKind { | ||||||||||||||||||||||||||||||||||||||||||||||
| int TOUCH = 0; | ||||||||||||||||||||||||||||||||||||||||||||||
| int MOUSE = 1; | ||||||||||||||||||||||||||||||||||||||||||||||
| int STYLUS = 2; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -59,15 +62,15 @@ 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; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // 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; | ||||||||||||||||||||||||||||||||||||||||||||||
| @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. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -76,12 +79,12 @@ 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; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| private final Map<Integer, float[]> 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 +223,28 @@ 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) { | ||||||||||||||||||||||||||||||||||||||||||||||
| // Some implementations translate trackpad scrolling into a mouse down-move-up event | ||||||||||||||||||||||||||||||||||||||||||||||
moffatman marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||
| // 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); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+231
to
+240
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just tested on a real device and found out that ChromeOS now converts trackpad scrolling into events with tool type "finger" instead of "mouse". I've therefore fixed the logic as below. This also makes these events especially distinctive.
Suggested change
As a result, running the demo at https://docs.flutter.dev/cookbook/effects/drag-a-widget#interactive-example, moving the cursor onto the menu and scrolling the trackpad now correctly scrolls the list instead of dragging the items. :D
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any concern that we have no idea which proportion of devices this will work on? I guess it's based on undocumented behaviour anyway, and Chrome OS is always up to date.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're required to make Flutter work on ChromeOS, so the portion of device shouldn't matter. Sure the behavior might change, and it's best we run an integration test, but I don't think we can achieve this in a short while. Maybe leave a TODO?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also maybe it's better to use
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Never mind, I'll handle this change in another PR. The current code passes all tests anyway. |
||||||||||||||||||||||||||||||||||||||||||||||
| } else if (pointerKind == PointerDeviceKind.STYLUS) { | ||||||||||||||||||||||||||||||||||||||||||||||
| buttons = (event.getButtonState() >> 4) & 0xF; | ||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||
| buttons = 0; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| boolean isTrackpadPan = ongoingPans.containsKey(event.getPointerId(pointerIndex)); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| int signalKind = | ||||||||||||||||||||||||||||||||||||||||||||||
| event.getActionMasked() == MotionEvent.ACTION_SCROLL | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -230,39 +255,31 @@ private void addPointerForIndex( | |||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| packet.putLong(motionEventId); // motionEventId | ||||||||||||||||||||||||||||||||||||||||||||||
| packet.putLong(timeStamp); // time_stamp | ||||||||||||||||||||||||||||||||||||||||||||||
| packet.putLong(pointerChange); // change | ||||||||||||||||||||||||||||||||||||||||||||||
| packet.putLong(pointerKind); // kind | ||||||||||||||||||||||||||||||||||||||||||||||
| if (isTrackpadPan) { | ||||||||||||||||||||||||||||||||||||||||||||||
| 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 (isTrackpadPan) { | ||||||||||||||||||||||||||||||||||||||||||||||
| 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 +334,22 @@ private void addPointerForIndex( | |||||||||||||||||||||||||||||||||||||||||||||
| packet.putDouble(0.0); // scroll_delta_x | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| packet.putDouble(0.0); // pan_x | ||||||||||||||||||||||||||||||||||||||||||||||
| packet.putDouble(0.0); // pan_y | ||||||||||||||||||||||||||||||||||||||||||||||
| if (isTrackpadPan) { | ||||||||||||||||||||||||||||||||||||||||||||||
| 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 (isTrackpadPan && getPointerChangeForPanZoom(pointerChange) == PointerChange.PAN_ZOOM_END) { | ||||||||||||||||||||||||||||||||||||||||||||||
| ongoingPans.remove(event.getPointerId(pointerIndex)); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @PointerChange | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -357,6 +384,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) { | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.