66import android .view .MotionEvent ;
77import androidx .annotation .IntDef ;
88import androidx .annotation .NonNull ;
9+ import androidx .annotation .VisibleForTesting ;
910import io .flutter .embedding .engine .renderer .FlutterRenderer ;
1011import java .nio .ByteBuffer ;
1112import java .nio .ByteOrder ;
13+ import java .util .HashMap ;
14+ import java .util .Map ;
1215
1316/** Sends touch information from Android to Flutter in a format that Flutter understands. */
1417public class AndroidTouchProcessor {
@@ -26,7 +29,7 @@ public class AndroidTouchProcessor {
2629 PointerChange .PAN_ZOOM_UPDATE ,
2730 PointerChange .PAN_ZOOM_END
2831 })
29- private @interface PointerChange {
32+ public @interface PointerChange {
3033 int CANCEL = 0 ;
3134 int ADD = 1 ;
3235 int REMOVE = 2 ;
@@ -48,7 +51,7 @@ public class AndroidTouchProcessor {
4851 PointerDeviceKind .TRACKPAD ,
4952 PointerDeviceKind .UNKNOWN
5053 })
51- private @interface PointerDeviceKind {
54+ public @interface PointerDeviceKind {
5255 int TOUCH = 0 ;
5356 int MOUSE = 1 ;
5457 int STYLUS = 2 ;
@@ -59,15 +62,15 @@ public class AndroidTouchProcessor {
5962
6063 // Must match the PointerSignalKind enum in pointer.dart.
6164 @ IntDef ({PointerSignalKind .NONE , PointerSignalKind .SCROLL , PointerSignalKind .UNKNOWN })
62- private @interface PointerSignalKind {
65+ public @interface PointerSignalKind {
6366 int NONE = 0 ;
6467 int SCROLL = 1 ;
6568 int UNKNOWN = 2 ;
6669 }
6770
6871 // Must match the unpacking code in hooks.dart.
6972 private static final int POINTER_DATA_FIELD_COUNT = 35 ;
70- private static final int BYTES_PER_FIELD = 8 ;
73+ @ VisibleForTesting static final int BYTES_PER_FIELD = 8 ;
7174
7275 // This value must match the value in framework's platform_view.dart.
7376 // This flag indicates whether the original Android pointer events were batched together.
@@ -76,12 +79,12 @@ public class AndroidTouchProcessor {
7679 @ NonNull private final FlutterRenderer renderer ;
7780 @ NonNull private final MotionEventTracker motionEventTracker ;
7881
79- private static final int _POINTER_BUTTON_PRIMARY = 1 ;
80-
8182 private static final Matrix IDENTITY_TRANSFORM = new Matrix ();
8283
8384 private final boolean trackMotionEvents ;
8485
86+ private final Map <Integer , float []> ongoingPans = new HashMap <>();
87+
8588 /**
8689 * Constructs an {@code AndroidTouchProcessor} that will send touch event data to the Flutter
8790 * execution context represented by the given {@link FlutterRenderer}.
@@ -220,6 +223,28 @@ private void addPointerForIndex(
220223 }
221224
222225 int pointerKind = getPointerDeviceTypeForToolType (event .getToolType (pointerIndex ));
226+ // We use this in lieu of using event.getRawX and event.getRawY as we wish to support
227+ // earlier versions than API level 29.
228+ float viewToScreenCoords [] = {event .getX (pointerIndex ), event .getY (pointerIndex )};
229+ transformMatrix .mapPoints (viewToScreenCoords );
230+ long buttons ;
231+ if (pointerKind == PointerDeviceKind .MOUSE ) {
232+ buttons = event .getButtonState () & 0x1F ;
233+ if (buttons == 0
234+ && event .getSource () == InputDevice .SOURCE_MOUSE
235+ && pointerChange == PointerChange .DOWN ) {
236+ // Some implementations translate trackpad scrolling into a mouse down-move-up event
237+ // sequence with buttons: 0, such as ARC on a Chromebook. See #11420, a legacy
238+ // implementation that uses the same condition but converts differently.
239+ ongoingPans .put (event .getPointerId (pointerIndex ), viewToScreenCoords );
240+ }
241+ } else if (pointerKind == PointerDeviceKind .STYLUS ) {
242+ buttons = (event .getButtonState () >> 4 ) & 0xF ;
243+ } else {
244+ buttons = 0 ;
245+ }
246+
247+ boolean isTrackpadPan = ongoingPans .containsKey (event .getPointerId (pointerIndex ));
223248
224249 int signalKind =
225250 event .getActionMasked () == MotionEvent .ACTION_SCROLL
@@ -230,39 +255,31 @@ private void addPointerForIndex(
230255
231256 packet .putLong (motionEventId ); // motionEventId
232257 packet .putLong (timeStamp ); // time_stamp
233- packet .putLong (pointerChange ); // change
234- packet .putLong (pointerKind ); // kind
258+ if (isTrackpadPan ) {
259+ packet .putLong (getPointerChangeForPanZoom (pointerChange )); // change
260+ packet .putLong (PointerDeviceKind .TRACKPAD ); // kind
261+ } else {
262+ packet .putLong (pointerChange ); // change
263+ packet .putLong (pointerKind ); // kind
264+ }
235265 packet .putLong (signalKind ); // signal_kind
236266 packet .putLong (event .getPointerId (pointerIndex )); // device
237267 packet .putLong (0 ); // pointer_identifier, will be generated in pointer_data_packet_converter.cc.
238268
239- // We use this in lieu of using event.getRawX and event.getRawY as we wish to support
240- // earlier versions than API level 29.
241- float viewToScreenCoords [] = {event .getX (pointerIndex ), event .getY (pointerIndex )};
242- transformMatrix .mapPoints (viewToScreenCoords );
243- packet .putDouble (viewToScreenCoords [0 ]); // physical_x
244- packet .putDouble (viewToScreenCoords [1 ]); // physical_y
269+ if (isTrackpadPan ) {
270+ float [] panStart = ongoingPans .get (event .getPointerId (pointerIndex ));
271+ packet .putDouble (panStart [0 ]);
272+ packet .putDouble (panStart [1 ]);
273+ } else {
274+ packet .putDouble (viewToScreenCoords [0 ]); // physical_x
275+ packet .putDouble (viewToScreenCoords [1 ]); // physical_y
276+ }
245277
246278 packet .putDouble (
247279 0.0 ); // physical_delta_x, will be generated in pointer_data_packet_converter.cc.
248280 packet .putDouble (
249281 0.0 ); // physical_delta_y, will be generated in pointer_data_packet_converter.cc.
250282
251- long buttons ;
252- if (pointerKind == PointerDeviceKind .MOUSE ) {
253- buttons = event .getButtonState () & 0x1F ;
254- // TODO(dkwingsmt): Remove this fix after implementing touchpad gestures
255- // https://github.com/flutter/flutter/issues/23604#issuecomment-524471152
256- if (buttons == 0
257- && event .getSource () == InputDevice .SOURCE_MOUSE
258- && (pointerChange == PointerChange .DOWN || pointerChange == PointerChange .MOVE )) {
259- buttons = _POINTER_BUTTON_PRIMARY ;
260- }
261- } else if (pointerKind == PointerDeviceKind .STYLUS ) {
262- buttons = (event .getButtonState () >> 4 ) & 0xF ;
263- } else {
264- buttons = 0 ;
265- }
266283 packet .putLong (buttons ); // buttons
267284
268285 packet .putLong (0 ); // obscured
@@ -317,12 +334,22 @@ private void addPointerForIndex(
317334 packet .putDouble (0.0 ); // scroll_delta_x
318335 }
319336
320- packet .putDouble (0.0 ); // pan_x
321- packet .putDouble (0.0 ); // pan_y
337+ if (isTrackpadPan ) {
338+ float [] panStart = ongoingPans .get (event .getPointerId (pointerIndex ));
339+ packet .putDouble (viewToScreenCoords [0 ] - panStart [0 ]);
340+ packet .putDouble (viewToScreenCoords [1 ] - panStart [1 ]);
341+ } else {
342+ packet .putDouble (0.0 ); // pan_x
343+ packet .putDouble (0.0 ); // pan_y
344+ }
322345 packet .putDouble (0.0 ); // pan_delta_x
323346 packet .putDouble (0.0 ); // pan_delta_y
324347 packet .putDouble (1.0 ); // scale
325348 packet .putDouble (0.0 ); // rotation
349+
350+ if (isTrackpadPan && getPointerChangeForPanZoom (pointerChange ) == PointerChange .PAN_ZOOM_END ) {
351+ ongoingPans .remove (event .getPointerId (pointerIndex ));
352+ }
326353 }
327354
328355 @ PointerChange
@@ -354,7 +381,19 @@ private int getPointerChangeForAction(int maskedAction) {
354381 if (maskedAction == MotionEvent .ACTION_SCROLL ) {
355382 return PointerChange .HOVER ;
356383 }
357- return -1 ;
384+ throw new AssertionError ("Unexpected masked action" );
385+ }
386+
387+ @ PointerChange
388+ private int getPointerChangeForPanZoom (int pointerChange ) {
389+ if (pointerChange == PointerChange .DOWN ) {
390+ return PointerChange .PAN_ZOOM_START ;
391+ } else if (pointerChange == PointerChange .MOVE ) {
392+ return PointerChange .PAN_ZOOM_UPDATE ;
393+ } else if (pointerChange == PointerChange .UP || pointerChange == PointerChange .CANCEL ) {
394+ return PointerChange .PAN_ZOOM_END ;
395+ }
396+ throw new AssertionError ("Unexpected pointer change" );
358397 }
359398
360399 @ PointerDeviceKind
0 commit comments