From 4fc91304e8e0a7721fdb4cc22c00689b560efcdb Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Wed, 9 Aug 2023 14:58:43 -0500 Subject: [PATCH 01/25] Pair progress --- .../android/AndroidTouchProcessor.java | 38 ++++++++++---- .../embedding/android/FlutterView.java | 3 +- .../android/io/flutter/view/FlutterView.java | 3 +- sky/packages/sky_engine/lib/_embedder.yaml | 50 +++++++++---------- 4 files changed, 57 insertions(+), 37 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index b3db5df1eb99b..7b05107aab6de 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -1,9 +1,11 @@ package io.flutter.embedding.android; +import android.content.Context; import android.graphics.Matrix; import android.os.Build; import android.view.InputDevice; import android.view.MotionEvent; +import android.view.ViewConfiguration; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -138,7 +140,8 @@ public boolean onTouchEvent(@NonNull MotionEvent event, @NonNull Matrix transfor || maskedAction == MotionEvent.ACTION_POINTER_UP); if (updateForSinglePointer) { // ACTION_DOWN and ACTION_POINTER_DOWN always apply to a single pointer only. - addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet); + addPointerForIndex( + event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet, null); } else if (updateForMultiplePointers) { // ACTION_UP and ACTION_POINTER_UP may contain position updates for other pointers. // We are converting these updates to move events here in order to preserve this data. @@ -147,18 +150,25 @@ public boolean onTouchEvent(@NonNull MotionEvent event, @NonNull Matrix transfor for (int p = 0; p < pointerCount; p++) { if (p != event.getActionIndex() && event.getToolType(p) == MotionEvent.TOOL_TYPE_FINGER) { addPointerForIndex( - event, p, PointerChange.MOVE, POINTER_DATA_FLAG_BATCHED, transformMatrix, packet); + event, + p, + PointerChange.MOVE, + POINTER_DATA_FLAG_BATCHED, + transformMatrix, + packet, + null); } } // It's important that we're sending the UP event last. This allows PlatformView // to correctly batch everything back into the original Android event if needed. - addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet); + addPointerForIndex( + event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet, null); } else { // ACTION_MOVE may not actually mean all pointers have moved // but it's the responsibility of a later part of the system to // ignore 0-deltas if desired. for (int p = 0; p < pointerCount; p++) { - addPointerForIndex(event, p, pointerChange, 0, transformMatrix, packet); + addPointerForIndex(event, p, pointerChange, 0, transformMatrix, packet, null); } } @@ -183,7 +193,7 @@ public boolean onTouchEvent(@NonNull MotionEvent event, @NonNull Matrix transfor * @param event The generic motion event being processed. * @return True if the event was handled. */ - public boolean onGenericMotionEvent(@NonNull MotionEvent event) { + public boolean onGenericMotionEvent(@NonNull MotionEvent event, Context context) { // Method isFromSource is only available in API 18+ (Jelly Bean MR2) // Mouse hover support is not implemented for API < 18. boolean isPointerEvent = @@ -203,7 +213,8 @@ public boolean onGenericMotionEvent(@NonNull MotionEvent event) { packet.order(ByteOrder.LITTLE_ENDIAN); // ACTION_HOVER_MOVE always applies to a single pointer only. - addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, IDENTITY_TRANSFORM, packet); + addPointerForIndex( + event, event.getActionIndex(), pointerChange, 0, IDENTITY_TRANSFORM, packet, context); if (packet.position() % (POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD) != 0) { throw new AssertionError("Packet position is not on field boundary."); } @@ -219,7 +230,8 @@ private void addPointerForIndex( int pointerChange, int pointerData, Matrix transformMatrix, - ByteBuffer packet) { + ByteBuffer packet, + Context context) { if (pointerChange == -1) { return; } @@ -335,11 +347,17 @@ private void addPointerForIndex( packet.putLong(pointerData); // platformData if (signalKind == PointerSignalKind.SCROLL) { - packet.putDouble(-event.getAxisValue(MotionEvent.AXIS_HSCROLL)); // scroll_delta_x - packet.putDouble(-event.getAxisValue(MotionEvent.AXIS_VSCROLL)); // scroll_delta_y + double verticalScaleFactor = 1.0; + if (context != null) { + verticalScaleFactor = ViewConfiguration.get(context).getScaledVerticalScrollFactor(); + } + packet.putDouble(-event.getAxisValue(MotionEvent.AXIS_X, pointerIndex)); // scroll_delta_x + packet.putDouble( + verticalScaleFactor + * -event.getAxisValue(MotionEvent.AXIS_Y, pointerIndex)); // scroll_delta_y } else { packet.putDouble(0.0); // scroll_delta_x - packet.putDouble(0.0); // scroll_delta_x + packet.putDouble(0.0); // scroll_delta_y } if (isTrackpadPan) { diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 45fae9f96456c..a0f902ff9b527 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -952,7 +952,8 @@ public boolean onTouchEvent(@NonNull MotionEvent event) { @Override public boolean onGenericMotionEvent(@NonNull MotionEvent event) { boolean handled = - isAttachedToFlutterEngine() && androidTouchProcessor.onGenericMotionEvent(event); + isAttachedToFlutterEngine() + && androidTouchProcessor.onGenericMotionEvent(event, getContext()); return handled ? true : super.onGenericMotionEvent(event); } diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 7d15aab7ad89d..46d91eefe6420 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -482,7 +482,8 @@ public boolean onHoverEvent(MotionEvent event) { */ @Override public boolean onGenericMotionEvent(MotionEvent event) { - boolean handled = isAttached() && androidTouchProcessor.onGenericMotionEvent(event); + boolean handled = + isAttached() && androidTouchProcessor.onGenericMotionEvent(event, getContext()); return handled ? true : super.onGenericMotionEvent(event); } diff --git a/sky/packages/sky_engine/lib/_embedder.yaml b/sky/packages/sky_engine/lib/_embedder.yaml index ab4dd851b6b6b..1abb5224edfbe 100644 --- a/sky/packages/sky_engine/lib/_embedder.yaml +++ b/sky/packages/sky_engine/lib/_embedder.yaml @@ -1,33 +1,33 @@ -# This file is suitable for use within the tree. A different _embedder.yaml -# is generated by the BUILD.gn in this directory. Changes here must be -# mirrored there. +# This file is generated by //flutter/sky/packages/sky_engine:_embedder_yaml +# Do not modify this file directly. Instead, update the build file. + embedded_libs: - "dart:async": "../../../../../third_party/dart/sdk/lib/async/async.dart" - "dart:collection": "../../../../../third_party/dart/sdk/lib/collection/collection.dart" - "dart:convert": "../../../../../third_party/dart/sdk/lib/convert/convert.dart" - "dart:core": "../../../../../third_party/dart/sdk/lib/core/core.dart" - "dart:developer": "../../../../../third_party/dart/sdk/lib/developer/developer.dart" - "dart:ffi": "../../../../../third_party/dart/sdk/lib/ffi/ffi.dart" - "dart:html": "../../../../../third_party/dart/sdk/lib/html/html_dart2js.dart" - "dart:io": "../../../../../third_party/dart/sdk/lib/io/io.dart" - "dart:isolate": "../../../../../third_party/dart/sdk/lib/isolate/isolate.dart" - "dart:js": "../../../../../third_party/dart/sdk/lib/js/js.dart" - "dart:js_interop": "../../../../../third_party/dart/sdk/lib/js_interop/js_interop.dart" - "dart:js_interop_unsafe": "../../../../../third_party/dart/sdk/lib/js_interop_unsafe/js_interop_unsafe.dart" - "dart:js_util": "../../../../../third_party/dart/sdk/lib/js_util/js_util.dart" - "dart:math": "../../../../../third_party/dart/sdk/lib/math/math.dart" - "dart:typed_data": "../../../../../third_party/dart/sdk/lib/typed_data/typed_data.dart" - "dart:ui": "../../../../lib/ui/ui.dart" - "dart:ui_web": "../../../../lib/web_ui/lib/ui_web/src/ui_web.dart" + "dart:async": "async/async.dart" + "dart:collection": "collection/collection.dart" + "dart:convert": "convert/convert.dart" + "dart:core": "core/core.dart" + "dart:developer": "developer/developer.dart" + "dart:ffi": "ffi/ffi.dart" + "dart:html": "html/html_dart2js.dart" + "dart:io": "io/io.dart" + "dart:isolate": "isolate/isolate.dart" + "dart:js": "js/js.dart" + "dart:js_interop": "js_interop/js_interop.dart" + "dart:js_interop_unsafe": "js_interop_unsafe/js_interop_unsafe.dart" + "dart:js_util": "js_util/js_util.dart" + "dart:math": "math/math.dart" + "dart:typed_data": "typed_data/typed_data.dart" + "dart:ui": "ui/ui.dart" + "dart:ui_web": "ui_web/ui_web.dart" - "dart:_http": "../../../../../third_party/dart/sdk/lib/_http/http.dart" - "dart:_interceptors": "../../../../../third_party/dart/sdk/lib/_interceptors/interceptors.dart" + "dart:_http": "_http/http.dart" + "dart:_interceptors": "_interceptors/interceptors.dart" # The _internal library is needed as some implementations bleed into the # public API, e.g. List being Iterable by virtue of implementing # EfficientLengthIterable. Not including this library yields analysis errors. - "dart:_internal": "../../../../../third_party/dart/sdk/lib/internal/internal.dart" + "dart:_internal": "internal/internal.dart" # The _js_annotations library is also needed for the same reasons as _internal. - "dart:_js_annotations": "../../../../../third_party/dart/sdk/lib/js/_js_annotations.dart" + "dart:_js_annotations": "_js_annotations/_js_annotations.dart" # The _js_types library is also needed for the same reasons as _internal. - "dart:_js_types": "../../../../../third_party/dart/sdk/lib/_internal/js_shared/lib/js_types.dart" + "dart:_js_types": "_js_types/js_types.dart" "dart:nativewrappers": "_empty.dart" From 17faa0dda032151d464ee4b36496685b69d4b3a4 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Tue, 15 Aug 2023 10:45:24 -0400 Subject: [PATCH 02/25] Working mouse scroll wheel, note flutter takes longer to "see" a mouse that then scrolls than android --- .../embedding/android/AndroidTouchProcessor.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index 7b05107aab6de..d2230ea3a7bc8 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -346,15 +346,23 @@ private void addPointerForIndex( packet.putLong(pointerData); // platformData + // See android scrollview for insperation. + // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/widget/ScrollView.java;l=930?q=ScrollView&ss=android%2Fplatform%2Fsuperproject%2Fmain if (signalKind == PointerSignalKind.SCROLL) { double verticalScaleFactor = 1.0; + double horizontalScaleFactor = 1.0; if (context != null) { + horizontalScaleFactor = ViewConfiguration.get(context).getScaledHorizontalScrollFactor(); verticalScaleFactor = ViewConfiguration.get(context).getScaledVerticalScrollFactor(); } - packet.putDouble(-event.getAxisValue(MotionEvent.AXIS_X, pointerIndex)); // scroll_delta_x - packet.putDouble( - verticalScaleFactor - * -event.getAxisValue(MotionEvent.AXIS_Y, pointerIndex)); // scroll_delta_y + // Without flipping the sign of the value scroll moves the opposite direction of android + final double horizontalScrollDistance = + horizontalScaleFactor * -event.getAxisValue(MotionEvent.AXIS_HSCROLL, pointerIndex); + final double verticalScrollDistance = + verticalScaleFactor * -event.getAxisValue(MotionEvent.AXIS_VSCROLL, pointerIndex); + + packet.putDouble(horizontalScrollDistance); // scroll_delta_x + packet.putDouble(verticalScrollDistance); // scroll_delta_y } else { packet.putDouble(0.0); // scroll_delta_x packet.putDouble(0.0); // scroll_delta_y From 95f5aa0fe525673b14ecdfa9ee93e4109baec776 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Tue, 15 Aug 2023 18:15:39 -0400 Subject: [PATCH 03/25] refactor packet method to have an override when context is null --- .../android/AndroidTouchProcessor.java | 20 ++++++++++++++----- 1 file changed, 15 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 d2230ea3a7bc8..04108438416d8 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -141,7 +141,7 @@ public boolean onTouchEvent(@NonNull MotionEvent event, @NonNull Matrix transfor if (updateForSinglePointer) { // ACTION_DOWN and ACTION_POINTER_DOWN always apply to a single pointer only. addPointerForIndex( - event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet, null); + event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet); } else if (updateForMultiplePointers) { // ACTION_UP and ACTION_POINTER_UP may contain position updates for other pointers. // We are converting these updates to move events here in order to preserve this data. @@ -155,20 +155,19 @@ public boolean onTouchEvent(@NonNull MotionEvent event, @NonNull Matrix transfor PointerChange.MOVE, POINTER_DATA_FLAG_BATCHED, transformMatrix, - packet, - null); + packet); } } // It's important that we're sending the UP event last. This allows PlatformView // to correctly batch everything back into the original Android event if needed. addPointerForIndex( - event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet, null); + event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet); } else { // ACTION_MOVE may not actually mean all pointers have moved // but it's the responsibility of a later part of the system to // ignore 0-deltas if desired. for (int p = 0; p < pointerCount; p++) { - addPointerForIndex(event, p, pointerChange, 0, transformMatrix, packet, null); + addPointerForIndex(event, p, pointerChange, 0, transformMatrix, packet); } } @@ -222,6 +221,17 @@ public boolean onGenericMotionEvent(@NonNull MotionEvent event, Context context) return true; } + /// Passes null for context when it is not available. Note that without context scroll wheel will not work. + private void addPointerForIndex( + MotionEvent event, + int pointerIndex, + int pointerChange, + int pointerData, + Matrix transformMatrix, + ByteBuffer packet) { + addPointerForIndex(event, pointerIndex, pointerChange, pointerData, transformMatrix, packet, null); + } + // TODO(mattcarroll): consider creating a PointerPacket class instead of using a procedure that // mutates inputs. private void addPointerForIndex( From 2b9686c6f67ac669eee6f9a4a598f3fd672ff47c Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Wed, 16 Aug 2023 10:40:50 -0400 Subject: [PATCH 04/25] Add unit test for scroll wheel --- .../android/AndroidTouchProcessor.java | 19 ++++--- .../android/AndroidTouchProcessorTest.java | 57 +++++++++++++++++++ 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index 04108438416d8..c53d2e751f88f 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -357,22 +357,25 @@ private void addPointerForIndex( packet.putLong(pointerData); // platformData // See android scrollview for insperation. - // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/widget/ScrollView.java;l=930?q=ScrollView&ss=android%2Fplatform%2Fsuperproject%2Fmain + // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/widget/ScrollView.java?q=function:onGenericMotionEvent%20filepath:widget%2FScrollView.java&ss=android%2Fplatform%2Fsuperproject%2Fmain if (signalKind == PointerSignalKind.SCROLL) { - double verticalScaleFactor = 1.0; - double horizontalScaleFactor = 1.0; + // Default if context is null, chosen to ensure reasonable speed scrolling. + double verticalScaleFactor = 48.0; + double horizontalScaleFactor = 48.0; if (context != null) { horizontalScaleFactor = ViewConfiguration.get(context).getScaledHorizontalScrollFactor(); verticalScaleFactor = ViewConfiguration.get(context).getScaledVerticalScrollFactor(); } - // Without flipping the sign of the value scroll moves the opposite direction of android - final double horizontalScrollDistance = + // We flip the sign of the scroll value below because it aligns the pixel value with the + // scroll direction in native android. + final double horizontalScrollPixels = horizontalScaleFactor * -event.getAxisValue(MotionEvent.AXIS_HSCROLL, pointerIndex); - final double verticalScrollDistance = + final double verticalScrollPixels = verticalScaleFactor * -event.getAxisValue(MotionEvent.AXIS_VSCROLL, pointerIndex); - packet.putDouble(horizontalScrollDistance); // scroll_delta_x - packet.putDouble(verticalScrollDistance); // scroll_delta_y + Log.w("AndroidTouchProcessor", "index: " + pointerIndex + " VSF: " + verticalScaleFactor + " Distance: " + verticalScrollPixels); + packet.putDouble(horizontalScrollPixels); // scroll_delta_x + packet.putDouble(verticalScrollPixels); // scroll_delta_y } else { packet.putDouble(0.0); // scroll_delta_x packet.putDouble(0.0); // scroll_delta_y 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 dd3b896c37512..a438acb5e077d 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -7,6 +7,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.Context; + import android.annotation.TargetApi; import android.view.InputDevice; import android.view.MotionEvent; @@ -22,6 +24,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; +import android.view.ViewConfiguration; +import androidx.test.core.app.ApplicationProvider; @Config(manifest = Config.NONE) @RunWith(AndroidJUnit4.class) @@ -58,6 +62,14 @@ private double readPointerPhysicalY(ByteBuffer buffer) { return buffer.getDouble(8 * AndroidTouchProcessor.BYTES_PER_FIELD); } + private double readScrollDeltaX(ByteBuffer buffer) { + return buffer.getDouble(27 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readScrollDeltaY(ByteBuffer buffer) { + return buffer.getDouble(28 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + private double readPointerPanX(ByteBuffer buffer) { return buffer.getDouble(29 * AndroidTouchProcessor.BYTES_PER_FIELD); } @@ -66,6 +78,11 @@ private double readPointerPanY(ByteBuffer buffer) { return buffer.getDouble(30 * AndroidTouchProcessor.BYTES_PER_FIELD); } + /// Utility method when trying to write a new test. Prefer named readPointerXXX. + private double readOffset(int offset, ByteBuffer buffer) { + return buffer.getDouble(offset * AndroidTouchProcessor.BYTES_PER_FIELD); + } + private class MotionEventMocker { int pointerId; int source; @@ -81,6 +98,11 @@ 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); + // Ensure that isFromSource does not auto default to false when source is passed in. + when(event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)).thenReturn(source == InputDevice.SOURCE_CLASS_POINTER); + when(event.isFromSource(InputDevice.SOURCE_MOUSE)).thenReturn(source == InputDevice.SOURCE_MOUSE); + when(event.isFromSource(InputDevice.SOURCE_STYLUS)).thenReturn(source == InputDevice.SOURCE_STYLUS); + when(event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)).thenReturn(source == InputDevice.SOURCE_TOUCHSCREEN); when(event.getPointerCount()).thenReturn(1); when(event.getActionMasked()).thenReturn(action); when(event.getActionIndex()).thenReturn(0); @@ -89,6 +111,8 @@ MotionEvent mockEvent(int action, float x, float y, int buttonState) { when(event.getX(0)).thenReturn(x); when(event.getY(0)).thenReturn(y); when(event.getToolType(0)).thenReturn(toolType); + when(event.getAxisValue(MotionEvent.AXIS_HSCROLL, pointerId)).thenReturn(x); + when(event.getAxisValue(MotionEvent.AXIS_VSCROLL, pointerId)).thenReturn(y); return event; } } @@ -218,4 +242,37 @@ public void unexpectedMaskedAction() { touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_BUTTON_PRESS, 0.0f, 0.0f, 0)); verify(mockRenderer, never()).dispatchPointerDataPacket(ByteBuffer.allocate(0), 0); } + + + @Test + public void scrollWheel() { + // Pointer id must be zero to match actionIndex in mocked event. + final int pointerId = 0; + MotionEventMocker mocker = + new MotionEventMocker(pointerId, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); + final float horizontalScrollValue = -1f; + final float verticalScrollValue = .5f; + final Context context = ApplicationProvider.getApplicationContext(); + final double horizontalScaleFactor = ViewConfiguration.get(context).getScaledHorizontalScrollFactor(); + final double verticalScaleFactor = ViewConfiguration.get(context).getScaledVerticalScrollFactor(); + // Zero verticalScaleFactor will cause this test to miss bugs. + assertEquals("zero horizontal scale factor", true, horizontalScaleFactor != 0); + assertEquals("zero vertical scale factor", true, verticalScaleFactor != 0); + + final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_SCROLL, horizontalScrollValue, verticalScrollValue, 1); + boolean handled = touchProcessor.onGenericMotionEvent(event, context); + + InOrder inOrder = inOrder(mockRenderer); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + ByteBuffer packet = packetCaptor.getValue(); + + assertEquals(-horizontalScrollValue * horizontalScaleFactor, readScrollDeltaX(packet)); + assertEquals(-verticalScrollValue * verticalScaleFactor, readScrollDeltaY(packet)); + verify(event).getAxisValue(MotionEvent.AXIS_HSCROLL, pointerId); + verify(event).getAxisValue(MotionEvent.AXIS_VSCROLL, pointerId); + + inOrder.verifyNoMoreInteractions(); + } } From 43619cc65f89feff66137294e409b23d61225e79 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Wed, 16 Aug 2023 10:43:29 -0400 Subject: [PATCH 05/25] Add unit test for scroll wheel --- .../android/AndroidTouchProcessor.java | 13 ++++---- .../android/AndroidTouchProcessorTest.java | 32 +++++++++++-------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index c53d2e751f88f..76d2691032897 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -140,8 +140,7 @@ public boolean onTouchEvent(@NonNull MotionEvent event, @NonNull Matrix transfor || maskedAction == MotionEvent.ACTION_POINTER_UP); if (updateForSinglePointer) { // ACTION_DOWN and ACTION_POINTER_DOWN always apply to a single pointer only. - addPointerForIndex( - event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet); + addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet); } else if (updateForMultiplePointers) { // ACTION_UP and ACTION_POINTER_UP may contain position updates for other pointers. // We are converting these updates to move events here in order to preserve this data. @@ -221,7 +220,8 @@ public boolean onGenericMotionEvent(@NonNull MotionEvent event, Context context) return true; } - /// Passes null for context when it is not available. Note that without context scroll wheel will not work. + /// Passes null for context when it is not available. Note that without context scroll wheel will + // not work. private void addPointerForIndex( MotionEvent event, int pointerIndex, @@ -229,8 +229,9 @@ private void addPointerForIndex( int pointerData, Matrix transformMatrix, ByteBuffer packet) { - addPointerForIndex(event, pointerIndex, pointerChange, pointerData, transformMatrix, packet, null); - } + addPointerForIndex( + event, pointerIndex, pointerChange, pointerData, transformMatrix, packet, null); + } // TODO(mattcarroll): consider creating a PointerPacket class instead of using a procedure that // mutates inputs. @@ -372,8 +373,6 @@ private void addPointerForIndex( horizontalScaleFactor * -event.getAxisValue(MotionEvent.AXIS_HSCROLL, pointerIndex); final double verticalScrollPixels = verticalScaleFactor * -event.getAxisValue(MotionEvent.AXIS_VSCROLL, pointerIndex); - - Log.w("AndroidTouchProcessor", "index: " + pointerIndex + " VSF: " + verticalScaleFactor + " Distance: " + verticalScrollPixels); packet.putDouble(horizontalScrollPixels); // scroll_delta_x packet.putDouble(verticalScrollPixels); // scroll_delta_y } else { 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 a438acb5e077d..e4955d25b6da3 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -7,11 +7,12 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.content.Context; - import android.annotation.TargetApi; +import android.content.Context; import android.view.InputDevice; import android.view.MotionEvent; +import android.view.ViewConfiguration; +import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.engine.renderer.FlutterRenderer; import java.nio.ByteBuffer; @@ -24,8 +25,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; -import android.view.ViewConfiguration; -import androidx.test.core.app.ApplicationProvider; @Config(manifest = Config.NONE) @RunWith(AndroidJUnit4.class) @@ -99,10 +98,14 @@ MotionEvent mockEvent(int action, float x, float y, int buttonState) { when(event.getDevice()).thenReturn(null); when(event.getSource()).thenReturn(source); // Ensure that isFromSource does not auto default to false when source is passed in. - when(event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)).thenReturn(source == InputDevice.SOURCE_CLASS_POINTER); - when(event.isFromSource(InputDevice.SOURCE_MOUSE)).thenReturn(source == InputDevice.SOURCE_MOUSE); - when(event.isFromSource(InputDevice.SOURCE_STYLUS)).thenReturn(source == InputDevice.SOURCE_STYLUS); - when(event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)).thenReturn(source == InputDevice.SOURCE_TOUCHSCREEN); + when(event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) + .thenReturn(source == InputDevice.SOURCE_CLASS_POINTER); + when(event.isFromSource(InputDevice.SOURCE_MOUSE)) + .thenReturn(source == InputDevice.SOURCE_MOUSE); + when(event.isFromSource(InputDevice.SOURCE_STYLUS)) + .thenReturn(source == InputDevice.SOURCE_STYLUS); + when(event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) + .thenReturn(source == InputDevice.SOURCE_TOUCHSCREEN); when(event.getPointerCount()).thenReturn(1); when(event.getActionMasked()).thenReturn(action); when(event.getActionIndex()).thenReturn(0); @@ -243,23 +246,26 @@ public void unexpectedMaskedAction() { verify(mockRenderer, never()).dispatchPointerDataPacket(ByteBuffer.allocate(0), 0); } - @Test public void scrollWheel() { // Pointer id must be zero to match actionIndex in mocked event. final int pointerId = 0; MotionEventMocker mocker = - new MotionEventMocker(pointerId, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); + new MotionEventMocker( + pointerId, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); final float horizontalScrollValue = -1f; final float verticalScrollValue = .5f; final Context context = ApplicationProvider.getApplicationContext(); - final double horizontalScaleFactor = ViewConfiguration.get(context).getScaledHorizontalScrollFactor(); - final double verticalScaleFactor = ViewConfiguration.get(context).getScaledVerticalScrollFactor(); + final double horizontalScaleFactor = + ViewConfiguration.get(context).getScaledHorizontalScrollFactor(); + final double verticalScaleFactor = + ViewConfiguration.get(context).getScaledVerticalScrollFactor(); // Zero verticalScaleFactor will cause this test to miss bugs. assertEquals("zero horizontal scale factor", true, horizontalScaleFactor != 0); assertEquals("zero vertical scale factor", true, verticalScaleFactor != 0); - final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_SCROLL, horizontalScrollValue, verticalScrollValue, 1); + final MotionEvent event = + mocker.mockEvent(MotionEvent.ACTION_SCROLL, horizontalScrollValue, verticalScrollValue, 1); boolean handled = touchProcessor.onGenericMotionEvent(event, context); InOrder inOrder = inOrder(mockRenderer); From aa01e980793a132172c121070222c42f710dc6e1 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Wed, 16 Aug 2023 10:44:00 -0400 Subject: [PATCH 06/25] Add unit test for scroll wheel --- .../embedding/android/AndroidTouchProcessor.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index 76d2691032897..d9d79a2288e4d 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -149,18 +149,12 @@ public boolean onTouchEvent(@NonNull MotionEvent event, @NonNull Matrix transfor for (int p = 0; p < pointerCount; p++) { if (p != event.getActionIndex() && event.getToolType(p) == MotionEvent.TOOL_TYPE_FINGER) { addPointerForIndex( - event, - p, - PointerChange.MOVE, - POINTER_DATA_FLAG_BATCHED, - transformMatrix, - packet); + event, p, PointerChange.MOVE, POINTER_DATA_FLAG_BATCHED, transformMatrix, packet); } } // It's important that we're sending the UP event last. This allows PlatformView // to correctly batch everything back into the original Android event if needed. - addPointerForIndex( - event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet); + addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet); } else { // ACTION_MOVE may not actually mean all pointers have moved // but it's the responsibility of a later part of the system to From 05420ee87d51e010e67fde8dcd339b0a5cbf0b18 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Wed, 16 Aug 2023 10:54:44 -0400 Subject: [PATCH 07/25] make pointer logic easier to read --- .../io/flutter/embedding/android/AndroidTouchProcessor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index d9d79a2288e4d..a663ab2632945 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -194,7 +194,9 @@ public boolean onGenericMotionEvent(@NonNull MotionEvent event, Context context) boolean isMovementEvent = (event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE || event.getActionMasked() == MotionEvent.ACTION_SCROLL); - if (!isPointerEvent || !isMovementEvent) { + if (isPointerEvent && isMovementEvent) { + continue; + } else { return false; } From e14b961678947e4d3b31ac5879078f2e95b1ccec Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Wed, 16 Aug 2023 10:57:48 -0400 Subject: [PATCH 08/25] revert unrelated file --- sky/packages/sky_engine/lib/_embedder.yaml | 50 +++++++++++----------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/sky/packages/sky_engine/lib/_embedder.yaml b/sky/packages/sky_engine/lib/_embedder.yaml index 1abb5224edfbe..ab4dd851b6b6b 100644 --- a/sky/packages/sky_engine/lib/_embedder.yaml +++ b/sky/packages/sky_engine/lib/_embedder.yaml @@ -1,33 +1,33 @@ -# This file is generated by //flutter/sky/packages/sky_engine:_embedder_yaml -# Do not modify this file directly. Instead, update the build file. - +# This file is suitable for use within the tree. A different _embedder.yaml +# is generated by the BUILD.gn in this directory. Changes here must be +# mirrored there. embedded_libs: - "dart:async": "async/async.dart" - "dart:collection": "collection/collection.dart" - "dart:convert": "convert/convert.dart" - "dart:core": "core/core.dart" - "dart:developer": "developer/developer.dart" - "dart:ffi": "ffi/ffi.dart" - "dart:html": "html/html_dart2js.dart" - "dart:io": "io/io.dart" - "dart:isolate": "isolate/isolate.dart" - "dart:js": "js/js.dart" - "dart:js_interop": "js_interop/js_interop.dart" - "dart:js_interop_unsafe": "js_interop_unsafe/js_interop_unsafe.dart" - "dart:js_util": "js_util/js_util.dart" - "dart:math": "math/math.dart" - "dart:typed_data": "typed_data/typed_data.dart" - "dart:ui": "ui/ui.dart" - "dart:ui_web": "ui_web/ui_web.dart" + "dart:async": "../../../../../third_party/dart/sdk/lib/async/async.dart" + "dart:collection": "../../../../../third_party/dart/sdk/lib/collection/collection.dart" + "dart:convert": "../../../../../third_party/dart/sdk/lib/convert/convert.dart" + "dart:core": "../../../../../third_party/dart/sdk/lib/core/core.dart" + "dart:developer": "../../../../../third_party/dart/sdk/lib/developer/developer.dart" + "dart:ffi": "../../../../../third_party/dart/sdk/lib/ffi/ffi.dart" + "dart:html": "../../../../../third_party/dart/sdk/lib/html/html_dart2js.dart" + "dart:io": "../../../../../third_party/dart/sdk/lib/io/io.dart" + "dart:isolate": "../../../../../third_party/dart/sdk/lib/isolate/isolate.dart" + "dart:js": "../../../../../third_party/dart/sdk/lib/js/js.dart" + "dart:js_interop": "../../../../../third_party/dart/sdk/lib/js_interop/js_interop.dart" + "dart:js_interop_unsafe": "../../../../../third_party/dart/sdk/lib/js_interop_unsafe/js_interop_unsafe.dart" + "dart:js_util": "../../../../../third_party/dart/sdk/lib/js_util/js_util.dart" + "dart:math": "../../../../../third_party/dart/sdk/lib/math/math.dart" + "dart:typed_data": "../../../../../third_party/dart/sdk/lib/typed_data/typed_data.dart" + "dart:ui": "../../../../lib/ui/ui.dart" + "dart:ui_web": "../../../../lib/web_ui/lib/ui_web/src/ui_web.dart" - "dart:_http": "_http/http.dart" - "dart:_interceptors": "_interceptors/interceptors.dart" + "dart:_http": "../../../../../third_party/dart/sdk/lib/_http/http.dart" + "dart:_interceptors": "../../../../../third_party/dart/sdk/lib/_interceptors/interceptors.dart" # The _internal library is needed as some implementations bleed into the # public API, e.g. List being Iterable by virtue of implementing # EfficientLengthIterable. Not including this library yields analysis errors. - "dart:_internal": "internal/internal.dart" + "dart:_internal": "../../../../../third_party/dart/sdk/lib/internal/internal.dart" # The _js_annotations library is also needed for the same reasons as _internal. - "dart:_js_annotations": "_js_annotations/_js_annotations.dart" + "dart:_js_annotations": "../../../../../third_party/dart/sdk/lib/js/_js_annotations.dart" # The _js_types library is also needed for the same reasons as _internal. - "dart:_js_types": "_js_types/js_types.dart" + "dart:_js_types": "../../../../../third_party/dart/sdk/lib/_internal/js_shared/lib/js_types.dart" "dart:nativewrappers": "_empty.dart" From 2ea445d9f67fe9e0e7f85aead8e21bbca0c253ea Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Wed, 16 Aug 2023 13:11:12 -0400 Subject: [PATCH 09/25] Fix merge, add timestamp test, code does not compile --- .../android/AndroidTouchProcessorTest.java | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 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 b65d234cbd5fd..4578763cad010 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -16,6 +16,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.engine.renderer.FlutterRenderer; import java.nio.ByteBuffer; +import java.util.concurrent.TimeUnit; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,6 +36,9 @@ public class AndroidTouchProcessorTest { AndroidTouchProcessor touchProcessor; @Captor ArgumentCaptor packetCaptor; @Captor ArgumentCaptor packetSizeCaptor; + // Used for mock events in SystemClock.uptimeMillis() time base. + // 2 days in milliseconds + final long eventTimeMilliseconds = 172800000; @Before public void setUp() { @@ -41,6 +46,10 @@ public void setUp() { touchProcessor = new AndroidTouchProcessor(mockRenderer, false); } + private long readTimeStamp(ByteBuffer buffer) { + return buffer.getLong(1 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + private long readPointerChange(ByteBuffer buffer) { return buffer.getLong(2 * AndroidTouchProcessor.BYTES_PER_FIELD); } @@ -77,7 +86,7 @@ private double readPointerPanY(ByteBuffer buffer) { return buffer.getDouble(30 * AndroidTouchProcessor.BYTES_PER_FIELD); } - /// Utility method when trying to write a new test. Prefer named readPointerXXX. + /// Utility method when trying to write a new test. Prefer named readXXX. private double readOffset(int offset, ByteBuffer buffer) { return buffer.getDouble(offset * AndroidTouchProcessor.BYTES_PER_FIELD); } @@ -97,6 +106,7 @@ 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.getEventTime()).thenReturn(eventTimeMilliseconds); // Ensure that isFromSource does not auto default to false when source is passed in. when(event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) .thenReturn(source == InputDevice.SOURCE_CLASS_POINTER); @@ -281,7 +291,29 @@ public void scrollWheel() { inOrder.verifyNoMoreInteractions(); } - + + @Test + public void timeStamp() { + final int pointerId = 0; + MotionEventMocker mocker = + new MotionEventMocker( + pointerId, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); + + final MotionEvent event = + mocker.mockEvent(MotionEvent.ACTION_SCROLL, 1f, 1f, 1); + boolean handled = touchProcessor.onGenericMotionEvent(event, null); + + InOrder inOrder = inOrder(mockRenderer); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + ByteBuffer packet = packetCaptor.getValue(); + + assertEquals(TimeUnit.MILLISECONDS.toMicros(eventTimeMilliseconds), readTimeStamp(packet)); + + inOrder.verifyNoMoreInteractions(); + } + @Test public void unexpectedPointerChange() { // Regression test for https://github.com/flutter/flutter/issues/129765 @@ -313,7 +345,7 @@ public void unexpectedPointerChange() { assertEquals(10.0, readPointerPanX(packet)); assertEquals(5.0, readPointerPanY(packet)); - touchProcessor.onGenericMotionEvent(mocker.mockEvent(MotionEvent.ACTION_SCROLL, 0.0f, 0.0f, 0)); + touchProcessor.onGenericMotionEvent(mocker.mockEvent(MotionEvent.ACTION_SCROLL, 0.0f, 0.0f, 0), null); inOrder .verify(mockRenderer) .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); From e76fec07e0a164152aecdad435cf5df33afd0bc2 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Wed, 16 Aug 2023 15:46:24 -0400 Subject: [PATCH 10/25] Make tests compile --- .../io/flutter/embedding/android/AndroidTouchProcessor.java | 5 +++-- .../embedding/android/AndroidTouchProcessorTest.java | 6 +++--- 2 files 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 0ffaf67380514..d52f03000efff 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -183,9 +183,10 @@ public boolean onTouchEvent(@NonNull MotionEvent event, @NonNull Matrix transfor * wheel movements, etc. * * @param event The generic motion event being processed. + * @param context For use by ViewConfiguration.get(context) to scale input. * @return True if the event was handled. */ - public boolean onGenericMotionEvent(@NonNull MotionEvent event, Context context) { + public boolean onGenericMotionEvent(@NonNull MotionEvent event, @NonNull Context context) { // Method isFromSource is only available in API 18+ (Jelly Bean MR2) // Mouse hover support is not implemented for API < 18. boolean isPointerEvent = @@ -195,7 +196,7 @@ public boolean onGenericMotionEvent(@NonNull MotionEvent event, Context context) (event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE || event.getActionMasked() == MotionEvent.ACTION_SCROLL); if (isPointerEvent && isMovementEvent) { - continue; + // Continue. } else { return false; } 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 4578763cad010..06b63ea675870 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -301,7 +301,7 @@ public void timeStamp() { final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_SCROLL, 1f, 1f, 1); - boolean handled = touchProcessor.onGenericMotionEvent(event, null); + boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); inOrder @@ -318,7 +318,7 @@ public void timeStamp() { public void unexpectedPointerChange() { // Regression test for https://github.com/flutter/flutter/issues/129765 MotionEventMocker mocker = - new MotionEventMocker(0, InputDevice.SOURCE_MOUSE, MotionEvent.TOOL_TYPE_MOUSE); + new MotionEventMocker(0, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_DOWN, 0.0f, 0.0f, 0)); InOrder inOrder = inOrder(mockRenderer); @@ -345,7 +345,7 @@ public void unexpectedPointerChange() { assertEquals(10.0, readPointerPanX(packet)); assertEquals(5.0, readPointerPanY(packet)); - touchProcessor.onGenericMotionEvent(mocker.mockEvent(MotionEvent.ACTION_SCROLL, 0.0f, 0.0f, 0), null); + touchProcessor.onGenericMotionEvent(mocker.mockEvent(MotionEvent.ACTION_SCROLL, 0.0f, 0.0f, 0), ApplicationProvider.getApplicationContext()); inOrder .verify(mockRenderer) .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); From 22e8a7774e5927eec948bb8ffcd0e4a28ef6ea9a Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 17 Aug 2023 10:41:51 -0400 Subject: [PATCH 11/25] Make tests pass --- .../embedding/android/AndroidTouchProcessorTest.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 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 06b63ea675870..26c98c2328081 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -107,15 +107,6 @@ MotionEvent mockEvent(int action, float x, float y, int buttonState) { when(event.getDevice()).thenReturn(null); when(event.getSource()).thenReturn(source); when(event.getEventTime()).thenReturn(eventTimeMilliseconds); - // Ensure that isFromSource does not auto default to false when source is passed in. - when(event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) - .thenReturn(source == InputDevice.SOURCE_CLASS_POINTER); - when(event.isFromSource(InputDevice.SOURCE_MOUSE)) - .thenReturn(source == InputDevice.SOURCE_MOUSE); - when(event.isFromSource(InputDevice.SOURCE_STYLUS)) - .thenReturn(source == InputDevice.SOURCE_STYLUS); - when(event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) - .thenReturn(source == InputDevice.SOURCE_TOUCHSCREEN); when(event.getPointerCount()).thenReturn(1); when(event.getActionMasked()).thenReturn(action); when(event.getActionIndex()).thenReturn(0); @@ -124,6 +115,7 @@ MotionEvent mockEvent(int action, float x, float y, int buttonState) { when(event.getX(0)).thenReturn(x); when(event.getY(0)).thenReturn(y); when(event.getToolType(0)).thenReturn(toolType); + when(event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)).thenReturn(true); when(event.getAxisValue(MotionEvent.AXIS_HSCROLL, pointerId)).thenReturn(x); when(event.getAxisValue(MotionEvent.AXIS_VSCROLL, pointerId)).thenReturn(y); return event; @@ -318,7 +310,7 @@ public void timeStamp() { public void unexpectedPointerChange() { // Regression test for https://github.com/flutter/flutter/issues/129765 MotionEventMocker mocker = - new MotionEventMocker(0, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); + new MotionEventMocker(0, InputDevice.SOURCE_MOUSE, MotionEvent.TOOL_TYPE_MOUSE); touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_DOWN, 0.0f, 0.0f, 0)); InOrder inOrder = inOrder(mockRenderer); From e7dfa9d437af7240f56ce0ae21314ac5e50fc9db Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 17 Aug 2023 11:13:28 -0400 Subject: [PATCH 12/25] add device test --- .../android/AndroidTouchProcessor.java | 17 ++++++------ .../android/AndroidTouchProcessorTest.java | 27 +++++++++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index d52f03000efff..98c3e3cf09dbf 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -243,6 +243,7 @@ private void addPointerForIndex( if (pointerChange == -1) { return; } + final int pointerId = event.getPointerId(pointerIndex); int pointerKind = getPointerDeviceTypeForToolType(event.getToolType(pointerIndex)); // We use this in lieu of using event.getRawX and event.getRawY as we wish to support @@ -258,7 +259,7 @@ private void addPointerForIndex( // 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); + ongoingPans.put(pointerId, viewToScreenCoords); } } else if (pointerKind == PointerDeviceKind.STYLUS) { buttons = (event.getButtonState() >> 4) & 0xF; @@ -267,7 +268,7 @@ private void addPointerForIndex( } int panZoomType = -1; - boolean isTrackpadPan = ongoingPans.containsKey(event.getPointerId(pointerIndex)); + boolean isTrackpadPan = ongoingPans.containsKey(pointerId); if (isTrackpadPan) { panZoomType = getPointerChangeForPanZoom(pointerChange); if (panZoomType == -1) { @@ -298,13 +299,13 @@ private void addPointerForIndex( packet.putLong(pointerKind); // kind } packet.putLong(signalKind); // signal_kind - packet.putLong(event.getPointerId(pointerIndex)); // device + packet.putLong(pointerId); // device packet.putLong(0); // pointer_identifier, will be generated in pointer_data_packet_converter.cc. if (isTrackpadPan) { - float[] panStart = ongoingPans.get(event.getPointerId(pointerIndex)); - packet.putDouble(panStart[0]); - packet.putDouble(panStart[1]); + float[] panStart = ongoingPans.get(pointerId); + packet.putDouble(panStart[0]); // physical_x + packet.putDouble(panStart[1]); // physical_y } else { packet.putDouble(viewToScreenCoords[0]); // physical_x packet.putDouble(viewToScreenCoords[1]); // physical_y @@ -385,7 +386,7 @@ private void addPointerForIndex( } if (isTrackpadPan) { - float[] panStart = ongoingPans.get(event.getPointerId(pointerIndex)); + float[] panStart = ongoingPans.get(pointerId); packet.putDouble(viewToScreenCoords[0] - panStart[0]); packet.putDouble(viewToScreenCoords[1] - panStart[1]); } else { @@ -398,7 +399,7 @@ private void addPointerForIndex( packet.putDouble(0.0); // rotation if (isTrackpadPan && (panZoomType == PointerChange.PAN_ZOOM_END)) { - ongoingPans.remove(event.getPointerId(pointerIndex)); + ongoingPans.remove(pointerId); } } 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 26c98c2328081..b62931319c5b8 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -62,6 +62,10 @@ private long readPointerSignalKind(ByteBuffer buffer) { return buffer.getLong(4 * AndroidTouchProcessor.BYTES_PER_FIELD); } + private long readDevice(ByteBuffer buffer) { + return buffer.getLong(5 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + private double readPointerPhysicalX(ByteBuffer buffer) { return buffer.getDouble(7 * AndroidTouchProcessor.BYTES_PER_FIELD); } @@ -306,6 +310,29 @@ public void timeStamp() { inOrder.verifyNoMoreInteractions(); } + @Test + public void device() { + final int pointerId = 2; + MotionEventMocker mocker = + new MotionEventMocker( + pointerId, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); + + final MotionEvent event = + mocker.mockEvent(MotionEvent.ACTION_SCROLL, 1f, 1f, 1); + boolean handled = touchProcessor.onTouchEvent(event); + + InOrder inOrder = inOrder(mockRenderer); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + ByteBuffer packet = packetCaptor.getValue(); + + assertEquals(pointerId, readDevice(packet)); + verify(event).getPointerId(0); + + inOrder.verifyNoMoreInteractions(); + } + @Test public void unexpectedPointerChange() { // Regression test for https://github.com/flutter/flutter/issues/129765 From 53ac27771793cfea1626aa289d3953b968e3150b Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 17 Aug 2023 11:41:17 -0400 Subject: [PATCH 13/25] Add tests for pressure, obscured and synth --- .../android/AndroidTouchProcessorTest.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) 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 b62931319c5b8..650dad14735d1 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -39,6 +39,7 @@ public class AndroidTouchProcessorTest { // Used for mock events in SystemClock.uptimeMillis() time base. // 2 days in milliseconds final long eventTimeMilliseconds = 172800000; + final float pressure = 0.8f; @Before public void setUp() { @@ -74,6 +75,18 @@ private double readPointerPhysicalY(ByteBuffer buffer) { return buffer.getDouble(8 * AndroidTouchProcessor.BYTES_PER_FIELD); } + private double readObscured(ByteBuffer buffer) { + return buffer.getDouble(12 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readSynthesized(ByteBuffer buffer) { + return buffer.getDouble(13 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readPressure(ByteBuffer buffer) { + return buffer.getDouble(14 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + private double readScrollDeltaX(ByteBuffer buffer) { return buffer.getDouble(27 * AndroidTouchProcessor.BYTES_PER_FIELD); } @@ -122,6 +135,7 @@ MotionEvent mockEvent(int action, float x, float y, int buttonState) { when(event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)).thenReturn(true); when(event.getAxisValue(MotionEvent.AXIS_HSCROLL, pointerId)).thenReturn(x); when(event.getAxisValue(MotionEvent.AXIS_VSCROLL, pointerId)).thenReturn(y); + when(event.getPressure(0)).thenReturn(pressure); return event; } } @@ -333,6 +347,92 @@ public void device() { inOrder.verifyNoMoreInteractions(); } + @Test + public void physicalXPhysicalY() { + MotionEventMocker mocker = + new MotionEventMocker( + 1, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); + final float x = 10.0f; + final float y = 20.0f; + final MotionEvent event = + mocker.mockEvent(MotionEvent.ACTION_DOWN, x, y, 0); + boolean handled = touchProcessor.onTouchEvent(event); + + InOrder inOrder = inOrder(mockRenderer); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + ByteBuffer packet = packetCaptor.getValue(); + + assertEquals((double)x, readPointerPhysicalX(packet)); + assertEquals((double)y, readPointerPhysicalY(packet)); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void obscured() { + MotionEventMocker mocker = + new MotionEventMocker( + 1, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); + final MotionEvent event = + mocker.mockEvent(MotionEvent.ACTION_DOWN, 10.0f, 20.0f, 0); + boolean handled = touchProcessor.onTouchEvent(event); + + InOrder inOrder = inOrder(mockRenderer); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + ByteBuffer packet = packetCaptor.getValue(); + + // Always zero. + assertEquals(0.0, readObscured(packet)); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void synthesized() { + MotionEventMocker mocker = + new MotionEventMocker( + 1, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); + final MotionEvent event = + mocker.mockEvent(MotionEvent.ACTION_DOWN, 10.0f, 20.0f, 0); + boolean handled = touchProcessor.onTouchEvent(event); + + InOrder inOrder = inOrder(mockRenderer); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + ByteBuffer packet = packetCaptor.getValue(); + + // Always zero. + assertEquals(0.0, readSynthesized(packet)); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void pressure() { + MotionEventMocker mocker = + new MotionEventMocker( + 1, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); + final MotionEvent event = + mocker.mockEvent(MotionEvent.ACTION_DOWN, 10.0f, 20.0f, 0); + boolean handled = touchProcessor.onTouchEvent(event); + + InOrder inOrder = inOrder(mockRenderer); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + ByteBuffer packet = packetCaptor.getValue(); + + // Always zero. + assertEquals((double)pressure, readPressure(packet)); + + inOrder.verifyNoMoreInteractions(); + } + @Test public void unexpectedPointerChange() { // Regression test for https://github.com/flutter/flutter/issues/129765 From 02e117686f3df4fbc5b8b1a928c39d8c3361a50e Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 17 Aug 2023 11:51:39 -0400 Subject: [PATCH 14/25] Add tests for pressure min and max --- .../embedding/android/AndroidTouchProcessorTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 650dad14735d1..166aab1b46c69 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -87,6 +87,14 @@ private double readPressure(ByteBuffer buffer) { return buffer.getDouble(14 * AndroidTouchProcessor.BYTES_PER_FIELD); } + private double readPressureMin(ByteBuffer buffer) { + return buffer.getDouble(15 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readPressureMax(ByteBuffer buffer) { + return buffer.getDouble(16 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + private double readScrollDeltaX(ByteBuffer buffer) { return buffer.getDouble(27 * AndroidTouchProcessor.BYTES_PER_FIELD); } @@ -429,6 +437,9 @@ public void pressure() { // Always zero. assertEquals((double)pressure, readPressure(packet)); + // Verify default range with null device. + assertEquals(0.0, readPressureMin(packet)); + assertEquals(1.0, readPressureMax(packet)); inOrder.verifyNoMoreInteractions(); } From 2c823c7bc2a6af240f03989a88623daed8daf30f Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 17 Aug 2023 12:12:08 -0400 Subject: [PATCH 15/25] Add test for stylus distance --- .../android/AndroidTouchProcessorTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) 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 166aab1b46c69..071b478651e3c 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -95,6 +95,14 @@ private double readPressureMax(ByteBuffer buffer) { return buffer.getDouble(16 * AndroidTouchProcessor.BYTES_PER_FIELD); } + private double readDistance(ByteBuffer buffer) { + return buffer.getDouble(17 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readDistanceMax(ByteBuffer buffer) { + return buffer.getDouble(18 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + private double readScrollDeltaX(ByteBuffer buffer) { return buffer.getDouble(27 * AndroidTouchProcessor.BYTES_PER_FIELD); } @@ -143,6 +151,8 @@ MotionEvent mockEvent(int action, float x, float y, int buttonState) { when(event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)).thenReturn(true); when(event.getAxisValue(MotionEvent.AXIS_HSCROLL, pointerId)).thenReturn(x); when(event.getAxisValue(MotionEvent.AXIS_VSCROLL, pointerId)).thenReturn(y); + // Use x value for convenience. + when(event.getAxisValue(MotionEvent.AXIS_DISTANCE, pointerId)).thenReturn(x); when(event.getPressure(0)).thenReturn(pressure); return event; } @@ -444,6 +454,29 @@ public void pressure() { inOrder.verifyNoMoreInteractions(); } + @Test + public void stylusDistance() { + MotionEventMocker mocker = + new MotionEventMocker( + 0, InputDevice.SOURCE_STYLUS, MotionEvent.TOOL_TYPE_STYLUS); + final float x = 10.0f; + final MotionEvent event = + mocker.mockEvent(MotionEvent.ACTION_DOWN, x, 20.0f, 0); + boolean handled = touchProcessor.onTouchEvent(event); + + InOrder inOrder = inOrder(mockRenderer); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + ByteBuffer packet = packetCaptor.getValue(); + assertEquals(AndroidTouchProcessor.PointerDeviceKind.STYLUS, readPointerDeviceKind(packet)); + assertEquals((double)x, readDistance(packet)); + // Always zero. + assertEquals(0.0, readDistanceMax(packet)); + + inOrder.verifyNoMoreInteractions(); + } + @Test public void unexpectedPointerChange() { // Regression test for https://github.com/flutter/flutter/issues/129765 From c2f3c56afd340d1ecaf87eced07eb8bfa2990c24 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 17 Aug 2023 12:21:45 -0400 Subject: [PATCH 16/25] Add test for stylus distance --- .../embedding/android/AndroidTouchProcessorTest.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 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 071b478651e3c..7f0440e7e1f7c 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -103,6 +103,10 @@ private double readDistanceMax(ByteBuffer buffer) { return buffer.getDouble(18 * AndroidTouchProcessor.BYTES_PER_FIELD); } + private double readStylusTilt(ByteBuffer buffer) { + return buffer.getDouble(25 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + private double readScrollDeltaX(ByteBuffer buffer) { return buffer.getDouble(27 * AndroidTouchProcessor.BYTES_PER_FIELD); } @@ -151,8 +155,9 @@ MotionEvent mockEvent(int action, float x, float y, int buttonState) { when(event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)).thenReturn(true); when(event.getAxisValue(MotionEvent.AXIS_HSCROLL, pointerId)).thenReturn(x); when(event.getAxisValue(MotionEvent.AXIS_VSCROLL, pointerId)).thenReturn(y); - // Use x value for convenience. + // Use x and y values for convenience. when(event.getAxisValue(MotionEvent.AXIS_DISTANCE, pointerId)).thenReturn(x); + when(event.getAxisValue(MotionEvent.AXIS_TILT, pointerId)).thenReturn(y); when(event.getPressure(0)).thenReturn(pressure); return event; } @@ -460,8 +465,9 @@ public void stylusDistance() { new MotionEventMocker( 0, InputDevice.SOURCE_STYLUS, MotionEvent.TOOL_TYPE_STYLUS); final float x = 10.0f; + final float y = 20.0f; final MotionEvent event = - mocker.mockEvent(MotionEvent.ACTION_DOWN, x, 20.0f, 0); + mocker.mockEvent(MotionEvent.ACTION_DOWN, x, y, 0); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); @@ -473,6 +479,7 @@ public void stylusDistance() { assertEquals((double)x, readDistance(packet)); // Always zero. assertEquals(0.0, readDistanceMax(packet)); + assertEquals((double)y, readStylusTilt(packet)); inOrder.verifyNoMoreInteractions(); } From 168164f91f653871c72f207ef93b48af4eb47f56 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 17 Aug 2023 13:04:28 -0400 Subject: [PATCH 17/25] Add tests for size and radius --- .../android/AndroidTouchProcessorTest.java | 80 ++++++++++++++++--- 1 file changed, 69 insertions(+), 11 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 7f0440e7e1f7c..cb346ee5f0257 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -16,6 +16,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.engine.renderer.FlutterRenderer; import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.concurrent.TimeUnit; import org.junit.Before; @@ -103,6 +104,26 @@ private double readDistanceMax(ByteBuffer buffer) { return buffer.getDouble(18 * AndroidTouchProcessor.BYTES_PER_FIELD); } + private double readSize(ByteBuffer buffer) { + return buffer.getDouble(19 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readRadiusMajor(ByteBuffer buffer) { + return buffer.getDouble(20 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readRadiusMinor(ByteBuffer buffer) { + return buffer.getDouble(21 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readRadiusMax(ByteBuffer buffer) { + return buffer.getDouble(22 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readRadiusMin(ByteBuffer buffer) { + return buffer.getDouble(23 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + private double readStylusTilt(ByteBuffer buffer) { return buffer.getDouble(25 * AndroidTouchProcessor.BYTES_PER_FIELD); } @@ -146,19 +167,24 @@ MotionEvent mockEvent(int action, float x, float y, int buttonState) { when(event.getEventTime()).thenReturn(eventTimeMilliseconds); when(event.getPointerCount()).thenReturn(1); when(event.getActionMasked()).thenReturn(action); - when(event.getActionIndex()).thenReturn(0); + final int actionIndex = 0; + when(event.getActionIndex()).thenReturn(actionIndex); 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); + when(event.getPointerId(actionIndex)).thenReturn(pointerId); + when(event.getX(actionIndex)).thenReturn(x); + when(event.getY(actionIndex)).thenReturn(y); + when(event.getToolType(actionIndex)).thenReturn(toolType); when(event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)).thenReturn(true); when(event.getAxisValue(MotionEvent.AXIS_HSCROLL, pointerId)).thenReturn(x); when(event.getAxisValue(MotionEvent.AXIS_VSCROLL, pointerId)).thenReturn(y); // Use x and y values for convenience. when(event.getAxisValue(MotionEvent.AXIS_DISTANCE, pointerId)).thenReturn(x); when(event.getAxisValue(MotionEvent.AXIS_TILT, pointerId)).thenReturn(y); - when(event.getPressure(0)).thenReturn(pressure); + when(event.getPressure(actionIndex)).thenReturn(pressure); + // Use x and y values for convenience. + when(event.getSize(actionIndex)).thenReturn(x); + when(event.getToolMajor(actionIndex)).thenReturn(x); + when(event.getToolMinor(actionIndex)).thenReturn(y); return event; } } @@ -464,10 +490,10 @@ public void stylusDistance() { MotionEventMocker mocker = new MotionEventMocker( 0, InputDevice.SOURCE_STYLUS, MotionEvent.TOOL_TYPE_STYLUS); - final float x = 10.0f; - final float y = 20.0f; + final float distance = 10.0f; + final float tilt = 20.0f; final MotionEvent event = - mocker.mockEvent(MotionEvent.ACTION_DOWN, x, y, 0); + mocker.mockEvent(MotionEvent.ACTION_DOWN, distance, tilt, 0); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); @@ -476,10 +502,42 @@ public void stylusDistance() { .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); ByteBuffer packet = packetCaptor.getValue(); assertEquals(AndroidTouchProcessor.PointerDeviceKind.STYLUS, readPointerDeviceKind(packet)); - assertEquals((double)x, readDistance(packet)); + assertEquals((double)distance, readDistance(packet)); // Always zero. assertEquals(0.0, readDistanceMax(packet)); - assertEquals((double)y, readStylusTilt(packet)); + assertEquals((double)tilt, readStylusTilt(packet)); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void sizeAndRadius() { + MotionEventMocker mocker = + new MotionEventMocker( + 0, InputDevice.SOURCE_STYLUS, MotionEvent.TOOL_TYPE_STYLUS); + final float size = 10.0f; + final float radiusMajor = size; + final float radiusMinor = 30.0f; + final MotionEvent event = + mocker.mockEvent(MotionEvent.ACTION_DOWN, size, radiusMinor, 0); + boolean handled = touchProcessor.onTouchEvent(event); + + InOrder inOrder = inOrder(mockRenderer); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + ByteBuffer packet = packetCaptor.getValue(); + + verify(event).getSize(0); + verify(event).getToolMajor(0); + verify(event).getToolMinor(0); + + assertEquals((double)size, readSize(packet)); + assertEquals((double)radiusMajor, readRadiusMajor(packet)); + assertEquals((double)radiusMinor, readRadiusMinor(packet)); + // Always zero. + assertEquals(0.0, readRadiusMin(packet)); + assertEquals(0.0, readRadiusMax(packet)); inOrder.verifyNoMoreInteractions(); } From 06cda9abaa1b7f762d31c16a7ac2776485662aa1 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 17 Aug 2023 13:09:15 -0400 Subject: [PATCH 18/25] Add tests for pan deltax and rotation and scale --- .../android/AndroidTouchProcessorTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) 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 cb346ee5f0257..c995e00c14b81 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -144,6 +144,22 @@ private double readPointerPanY(ByteBuffer buffer) { return buffer.getDouble(30 * AndroidTouchProcessor.BYTES_PER_FIELD); } + private double readPointerPanDeltaX(ByteBuffer buffer) { + return buffer.getDouble(31 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readPointerPanDeltaY(ByteBuffer buffer) { + return buffer.getDouble(32 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readScale(ByteBuffer buffer) { + return buffer.getDouble(33 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + + private double readRotation(ByteBuffer buffer) { + return buffer.getDouble(34 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + /// Utility method when trying to write a new test. Prefer named readXXX. private double readOffset(int offset, ByteBuffer buffer) { return buffer.getDouble(offset * AndroidTouchProcessor.BYTES_PER_FIELD); @@ -254,6 +270,12 @@ public void trackpadGesture() { assertEquals(0.0, readPointerPhysicalY(packet)); assertEquals(10.0, readPointerPanX(packet)); assertEquals(5.0, readPointerPanY(packet)); + // Always zero. + assertEquals(0.0, readPointerPanDeltaX(packet)); + assertEquals(0.0, readPointerPanDeltaY(packet)); + assertEquals(0.0, readRotation(packet)); + // Always 1. + assertEquals(1.0, readScale(packet)); touchProcessor.onTouchEvent(mocker.mockEvent(MotionEvent.ACTION_UP, 10.0f, 5.0f, 0)); inOrder .verify(mockRenderer) From bb157fb757ce630c51648d9e97e994afd324471a Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 17 Aug 2023 13:10:26 -0400 Subject: [PATCH 19/25] formatting --- .../android/AndroidTouchProcessorTest.java | 64 ++++++++----------- 1 file changed, 25 insertions(+), 39 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 c995e00c14b81..efb9b7f047f65 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -16,9 +16,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.engine.renderer.FlutterRenderer; import java.nio.ByteBuffer; -import java.util.Arrays; import java.util.concurrent.TimeUnit; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -380,8 +378,7 @@ public void timeStamp() { new MotionEventMocker( pointerId, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); - final MotionEvent event = - mocker.mockEvent(MotionEvent.ACTION_SCROLL, 1f, 1f, 1); + final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_SCROLL, 1f, 1f, 1); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); @@ -402,8 +399,7 @@ public void device() { new MotionEventMocker( pointerId, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); - final MotionEvent event = - mocker.mockEvent(MotionEvent.ACTION_SCROLL, 1f, 1f, 1); + final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_SCROLL, 1f, 1f, 1); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); @@ -421,12 +417,10 @@ public void device() { @Test public void physicalXPhysicalY() { MotionEventMocker mocker = - new MotionEventMocker( - 1, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); + new MotionEventMocker(1, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); final float x = 10.0f; final float y = 20.0f; - final MotionEvent event = - mocker.mockEvent(MotionEvent.ACTION_DOWN, x, y, 0); + final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_DOWN, x, y, 0); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); @@ -435,8 +429,8 @@ public void physicalXPhysicalY() { .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); ByteBuffer packet = packetCaptor.getValue(); - assertEquals((double)x, readPointerPhysicalX(packet)); - assertEquals((double)y, readPointerPhysicalY(packet)); + assertEquals((double) x, readPointerPhysicalX(packet)); + assertEquals((double) y, readPointerPhysicalY(packet)); inOrder.verifyNoMoreInteractions(); } @@ -444,10 +438,8 @@ public void physicalXPhysicalY() { @Test public void obscured() { MotionEventMocker mocker = - new MotionEventMocker( - 1, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); - final MotionEvent event = - mocker.mockEvent(MotionEvent.ACTION_DOWN, 10.0f, 20.0f, 0); + new MotionEventMocker(1, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); + final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_DOWN, 10.0f, 20.0f, 0); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); @@ -465,10 +457,8 @@ public void obscured() { @Test public void synthesized() { MotionEventMocker mocker = - new MotionEventMocker( - 1, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); - final MotionEvent event = - mocker.mockEvent(MotionEvent.ACTION_DOWN, 10.0f, 20.0f, 0); + new MotionEventMocker(1, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); + final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_DOWN, 10.0f, 20.0f, 0); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); @@ -486,10 +476,8 @@ public void synthesized() { @Test public void pressure() { MotionEventMocker mocker = - new MotionEventMocker( - 1, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); - final MotionEvent event = - mocker.mockEvent(MotionEvent.ACTION_DOWN, 10.0f, 20.0f, 0); + new MotionEventMocker(1, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); + final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_DOWN, 10.0f, 20.0f, 0); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); @@ -499,7 +487,7 @@ public void pressure() { ByteBuffer packet = packetCaptor.getValue(); // Always zero. - assertEquals((double)pressure, readPressure(packet)); + assertEquals((double) pressure, readPressure(packet)); // Verify default range with null device. assertEquals(0.0, readPressureMin(packet)); assertEquals(1.0, readPressureMax(packet)); @@ -510,12 +498,10 @@ public void pressure() { @Test public void stylusDistance() { MotionEventMocker mocker = - new MotionEventMocker( - 0, InputDevice.SOURCE_STYLUS, MotionEvent.TOOL_TYPE_STYLUS); + new MotionEventMocker(0, InputDevice.SOURCE_STYLUS, MotionEvent.TOOL_TYPE_STYLUS); final float distance = 10.0f; final float tilt = 20.0f; - final MotionEvent event = - mocker.mockEvent(MotionEvent.ACTION_DOWN, distance, tilt, 0); + final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_DOWN, distance, tilt, 0); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); @@ -524,10 +510,10 @@ public void stylusDistance() { .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); ByteBuffer packet = packetCaptor.getValue(); assertEquals(AndroidTouchProcessor.PointerDeviceKind.STYLUS, readPointerDeviceKind(packet)); - assertEquals((double)distance, readDistance(packet)); + assertEquals((double) distance, readDistance(packet)); // Always zero. assertEquals(0.0, readDistanceMax(packet)); - assertEquals((double)tilt, readStylusTilt(packet)); + assertEquals((double) tilt, readStylusTilt(packet)); inOrder.verifyNoMoreInteractions(); } @@ -535,13 +521,11 @@ public void stylusDistance() { @Test public void sizeAndRadius() { MotionEventMocker mocker = - new MotionEventMocker( - 0, InputDevice.SOURCE_STYLUS, MotionEvent.TOOL_TYPE_STYLUS); + new MotionEventMocker(0, InputDevice.SOURCE_STYLUS, MotionEvent.TOOL_TYPE_STYLUS); final float size = 10.0f; final float radiusMajor = size; final float radiusMinor = 30.0f; - final MotionEvent event = - mocker.mockEvent(MotionEvent.ACTION_DOWN, size, radiusMinor, 0); + final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_DOWN, size, radiusMinor, 0); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); @@ -554,9 +538,9 @@ public void sizeAndRadius() { verify(event).getToolMajor(0); verify(event).getToolMinor(0); - assertEquals((double)size, readSize(packet)); - assertEquals((double)radiusMajor, readRadiusMajor(packet)); - assertEquals((double)radiusMinor, readRadiusMinor(packet)); + assertEquals((double) size, readSize(packet)); + assertEquals((double) radiusMajor, readRadiusMajor(packet)); + assertEquals((double) radiusMinor, readRadiusMinor(packet)); // Always zero. assertEquals(0.0, readRadiusMin(packet)); assertEquals(0.0, readRadiusMax(packet)); @@ -595,7 +579,9 @@ public void unexpectedPointerChange() { assertEquals(10.0, readPointerPanX(packet)); assertEquals(5.0, readPointerPanY(packet)); - touchProcessor.onGenericMotionEvent(mocker.mockEvent(MotionEvent.ACTION_SCROLL, 0.0f, 0.0f, 0), ApplicationProvider.getApplicationContext()); + touchProcessor.onGenericMotionEvent( + mocker.mockEvent(MotionEvent.ACTION_SCROLL, 0.0f, 0.0f, 0), + ApplicationProvider.getApplicationContext()); inOrder .verify(mockRenderer) .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); From 01e8ebb2e4971952d5696d5591e1726c8e74edf7 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Fri, 18 Aug 2023 11:45:22 -0400 Subject: [PATCH 20/25] Add test for buttons, specifically stylus --- .../embedding/android/AndroidTouchProcessor.java | 4 ++++ .../android/AndroidTouchProcessorTest.java | 14 +++++++++++--- 2 files changed, 15 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 98c3e3cf09dbf..8e7f72b7e7fa5 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -262,6 +262,10 @@ private void addPointerForIndex( ongoingPans.put(pointerId, viewToScreenCoords); } } else if (pointerKind == PointerDeviceKind.STYLUS) { + // Returns converted android button state into flutter framework normalized state + // and updates ongoingPans for chromebook trackpad scrolling. + // See https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/events.dart + // for target button constants. buttons = (event.getButtonState() >> 4) & 0xF; } else { buttons = 0; 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 efb9b7f047f65..8f491a9a5ad91 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -39,6 +39,8 @@ public class AndroidTouchProcessorTest { // 2 days in milliseconds final long eventTimeMilliseconds = 172800000; final float pressure = 0.8f; + // https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/events.dart + final int enginePrimaryStylusButton = 0x02; @Before public void setUp() { @@ -74,6 +76,10 @@ private double readPointerPhysicalY(ByteBuffer buffer) { return buffer.getDouble(8 * AndroidTouchProcessor.BYTES_PER_FIELD); } + private long readButtons(ByteBuffer buffer) { + return buffer.getLong(11 * AndroidTouchProcessor.BYTES_PER_FIELD); + } + private double readObscured(ByteBuffer buffer) { return buffer.getDouble(12 * AndroidTouchProcessor.BYTES_PER_FIELD); } @@ -114,11 +120,11 @@ private double readRadiusMinor(ByteBuffer buffer) { return buffer.getDouble(21 * AndroidTouchProcessor.BYTES_PER_FIELD); } - private double readRadiusMax(ByteBuffer buffer) { + private double readRadiusMin(ByteBuffer buffer) { return buffer.getDouble(22 * AndroidTouchProcessor.BYTES_PER_FIELD); } - private double readRadiusMin(ByteBuffer buffer) { + private double readRadiusMax(ByteBuffer buffer) { return buffer.getDouble(23 * AndroidTouchProcessor.BYTES_PER_FIELD); } @@ -495,13 +501,14 @@ public void pressure() { inOrder.verifyNoMoreInteractions(); } + @Test public void stylusDistance() { MotionEventMocker mocker = new MotionEventMocker(0, InputDevice.SOURCE_STYLUS, MotionEvent.TOOL_TYPE_STYLUS); final float distance = 10.0f; final float tilt = 20.0f; - final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_DOWN, distance, tilt, 0); + final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_DOWN, distance, tilt, MotionEvent.BUTTON_STYLUS_PRIMARY); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); @@ -514,6 +521,7 @@ public void stylusDistance() { // Always zero. assertEquals(0.0, readDistanceMax(packet)); assertEquals((double) tilt, readStylusTilt(packet)); + assertEquals(enginePrimaryStylusButton, readButtons(packet)); inOrder.verifyNoMoreInteractions(); } From 8f3aa720edeb26b1472be6460aff733dc77be15e Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Fri, 18 Aug 2023 11:46:08 -0400 Subject: [PATCH 21/25] formatting --- .../io/flutter/embedding/android/AndroidTouchProcessor.java | 3 ++- .../flutter/embedding/android/AndroidTouchProcessorTest.java | 5 +++-- 2 files changed, 5 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 8e7f72b7e7fa5..269380e8c18ff 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -264,7 +264,8 @@ private void addPointerForIndex( } else if (pointerKind == PointerDeviceKind.STYLUS) { // Returns converted android button state into flutter framework normalized state // and updates ongoingPans for chromebook trackpad scrolling. - // See https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/events.dart + // See + // https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/events.dart // for target button constants. buttons = (event.getButtonState() >> 4) & 0xF; } else { 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 8f491a9a5ad91..fc5343c5cae32 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -501,14 +501,15 @@ public void pressure() { inOrder.verifyNoMoreInteractions(); } - @Test public void stylusDistance() { MotionEventMocker mocker = new MotionEventMocker(0, InputDevice.SOURCE_STYLUS, MotionEvent.TOOL_TYPE_STYLUS); final float distance = 10.0f; final float tilt = 20.0f; - final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_DOWN, distance, tilt, MotionEvent.BUTTON_STYLUS_PRIMARY); + final MotionEvent event = + mocker.mockEvent( + MotionEvent.ACTION_DOWN, distance, tilt, MotionEvent.BUTTON_STYLUS_PRIMARY); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); From eb4dbb06478fa4ab8d90f76c8de1646b8c88d4b1 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Fri, 18 Aug 2023 12:18:55 -0400 Subject: [PATCH 22/25] extract mock event values into function --- .../android/AndroidTouchProcessor.java | 5 ++- .../android/AndroidTouchProcessorTest.java | 44 +++++++++++-------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index 269380e8c18ff..1e6010e55f789 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -217,8 +217,9 @@ public boolean onGenericMotionEvent(@NonNull MotionEvent event, @NonNull Context return true; } - /// Passes null for context when it is not available. Note that without context scroll wheel will - // not work. + /// Calls addPointerForIndex with null for context. + /// + /// Without context the scroll wheel will not mimick android's scroll speed. private void addPointerForIndex( MotionEvent event, int pointerIndex, 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 fc5343c5cae32..ede55749a1ce0 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -164,11 +164,6 @@ private double readRotation(ByteBuffer buffer) { return buffer.getDouble(34 * AndroidTouchProcessor.BYTES_PER_FIELD); } - /// Utility method when trying to write a new test. Prefer named readXXX. - private double readOffset(int offset, ByteBuffer buffer) { - return buffer.getDouble(offset * AndroidTouchProcessor.BYTES_PER_FIELD); - } - private class MotionEventMocker { int pointerId; int source; @@ -179,8 +174,21 @@ private class MotionEventMocker { this.source = source; this.toolType = toolType; } - MotionEvent mockEvent(int action, float x, float y, int buttonState) { + return mockEvent(action, x, y, buttonState, x, y, x, y, x, x, y); + } + MotionEvent mockEvent( + int action, + float x, + float y, + int buttonState, + float hScroll, + float vScroll, + float axisDistance, + float axisTilt, + float size, + float toolMajor, + float toolMinor) { MotionEvent event = mock(MotionEvent.class); when(event.getDevice()).thenReturn(null); when(event.getSource()).thenReturn(source); @@ -195,16 +203,14 @@ MotionEvent mockEvent(int action, float x, float y, int buttonState) { when(event.getY(actionIndex)).thenReturn(y); when(event.getToolType(actionIndex)).thenReturn(toolType); when(event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)).thenReturn(true); - when(event.getAxisValue(MotionEvent.AXIS_HSCROLL, pointerId)).thenReturn(x); - when(event.getAxisValue(MotionEvent.AXIS_VSCROLL, pointerId)).thenReturn(y); - // Use x and y values for convenience. - when(event.getAxisValue(MotionEvent.AXIS_DISTANCE, pointerId)).thenReturn(x); - when(event.getAxisValue(MotionEvent.AXIS_TILT, pointerId)).thenReturn(y); + when(event.getAxisValue(MotionEvent.AXIS_HSCROLL, pointerId)).thenReturn(hScroll); + when(event.getAxisValue(MotionEvent.AXIS_VSCROLL, pointerId)).thenReturn(vScroll); + when(event.getAxisValue(MotionEvent.AXIS_DISTANCE, pointerId)).thenReturn(axisDistance); + when(event.getAxisValue(MotionEvent.AXIS_TILT, pointerId)).thenReturn(axisTilt); when(event.getPressure(actionIndex)).thenReturn(pressure); - // Use x and y values for convenience. - when(event.getSize(actionIndex)).thenReturn(x); - when(event.getToolMajor(actionIndex)).thenReturn(x); - when(event.getToolMinor(actionIndex)).thenReturn(y); + when(event.getSize(actionIndex)).thenReturn(size); + when(event.getToolMajor(actionIndex)).thenReturn(toolMajor); + when(event.getToolMinor(actionIndex)).thenReturn(toolMinor); return event; } } @@ -360,7 +366,7 @@ public void scrollWheel() { assertEquals("zero vertical scale factor", true, verticalScaleFactor != 0); final MotionEvent event = - mocker.mockEvent(MotionEvent.ACTION_SCROLL, horizontalScrollValue, verticalScrollValue, 1); + mocker.mockEvent(MotionEvent.ACTION_SCROLL, 0.0f, 0.0f, 1, horizontalScrollValue, verticalScrollValue, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); boolean handled = touchProcessor.onGenericMotionEvent(event, context); InOrder inOrder = inOrder(mockRenderer); @@ -509,7 +515,7 @@ public void stylusDistance() { final float tilt = 20.0f; final MotionEvent event = mocker.mockEvent( - MotionEvent.ACTION_DOWN, distance, tilt, MotionEvent.BUTTON_STYLUS_PRIMARY); + MotionEvent.ACTION_DOWN, 0.0f, 0.0f, MotionEvent.BUTTON_STYLUS_PRIMARY, 0.0f, 0.0f, distance, tilt, 0.0f, 0.0f, 0.0f); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); @@ -532,9 +538,9 @@ public void sizeAndRadius() { MotionEventMocker mocker = new MotionEventMocker(0, InputDevice.SOURCE_STYLUS, MotionEvent.TOOL_TYPE_STYLUS); final float size = 10.0f; - final float radiusMajor = size; + final float radiusMajor = 20.0f; final float radiusMinor = 30.0f; - final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_DOWN, size, radiusMinor, 0); + final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_DOWN, 0.0f, 0.0f, 0, 0.0f, 0.0f, 0.0f, 0.0f, size, radiusMajor, radiusMinor); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); From 33be4afb75605ab165da3bc80453dc1e91c077d8 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Fri, 18 Aug 2023 12:19:39 -0400 Subject: [PATCH 23/25] formatting --- .../android/AndroidTouchProcessorTest.java | 63 ++++++++++++++----- 1 file changed, 49 insertions(+), 14 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 ede55749a1ce0..5f7ef456ba5c6 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -174,21 +174,23 @@ private class MotionEventMocker { this.source = source; this.toolType = toolType; } + MotionEvent mockEvent(int action, float x, float y, int buttonState) { return mockEvent(action, x, y, buttonState, x, y, x, y, x, x, y); } + MotionEvent mockEvent( - int action, - float x, - float y, - int buttonState, - float hScroll, - float vScroll, - float axisDistance, - float axisTilt, - float size, - float toolMajor, - float toolMinor) { + int action, + float x, + float y, + int buttonState, + float hScroll, + float vScroll, + float axisDistance, + float axisTilt, + float size, + float toolMajor, + float toolMinor) { MotionEvent event = mock(MotionEvent.class); when(event.getDevice()).thenReturn(null); when(event.getSource()).thenReturn(source); @@ -366,7 +368,18 @@ public void scrollWheel() { assertEquals("zero vertical scale factor", true, verticalScaleFactor != 0); final MotionEvent event = - mocker.mockEvent(MotionEvent.ACTION_SCROLL, 0.0f, 0.0f, 1, horizontalScrollValue, verticalScrollValue, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + mocker.mockEvent( + MotionEvent.ACTION_SCROLL, + 0.0f, + 0.0f, + 1, + horizontalScrollValue, + verticalScrollValue, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f); boolean handled = touchProcessor.onGenericMotionEvent(event, context); InOrder inOrder = inOrder(mockRenderer); @@ -515,7 +528,17 @@ public void stylusDistance() { final float tilt = 20.0f; final MotionEvent event = mocker.mockEvent( - MotionEvent.ACTION_DOWN, 0.0f, 0.0f, MotionEvent.BUTTON_STYLUS_PRIMARY, 0.0f, 0.0f, distance, tilt, 0.0f, 0.0f, 0.0f); + MotionEvent.ACTION_DOWN, + 0.0f, + 0.0f, + MotionEvent.BUTTON_STYLUS_PRIMARY, + 0.0f, + 0.0f, + distance, + tilt, + 0.0f, + 0.0f, + 0.0f); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); @@ -540,7 +563,19 @@ public void sizeAndRadius() { final float size = 10.0f; final float radiusMajor = 20.0f; final float radiusMinor = 30.0f; - final MotionEvent event = mocker.mockEvent(MotionEvent.ACTION_DOWN, 0.0f, 0.0f, 0, 0.0f, 0.0f, 0.0f, 0.0f, size, radiusMajor, radiusMinor); + final MotionEvent event = + mocker.mockEvent( + MotionEvent.ACTION_DOWN, + 0.0f, + 0.0f, + 0, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + size, + radiusMajor, + radiusMinor); boolean handled = touchProcessor.onTouchEvent(event); InOrder inOrder = inOrder(mockRenderer); From 2cd1f465ba6f5659f4a979240ca7b272b822cea3 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Fri, 18 Aug 2023 15:55:07 -0400 Subject: [PATCH 24/25] Tests for pre 26 scroll behavior --- .../android/AndroidTouchProcessor.java | 60 +++++++++++++++--- .../android/AndroidTouchProcessorTest.java | 62 ++++++++++++++++++- 2 files changed, 114 insertions(+), 8 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index 1e6010e55f789..5a4a9c1b9da40 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -1,8 +1,10 @@ package io.flutter.embedding.android; +import android.annotation.TargetApi; import android.content.Context; import android.graphics.Matrix; import android.os.Build; +import android.util.TypedValue; import android.view.InputDevice; import android.view.MotionEvent; import android.view.ViewConfiguration; @@ -82,6 +84,10 @@ public class AndroidTouchProcessor { private static final int POINTER_DATA_FIELD_COUNT = 35; @VisibleForTesting static final int BYTES_PER_FIELD = 8; + // Default if context is null, chosen to ensure reasonable speed scrolling. + @VisibleForTesting static final int DEFAULT_VERTICAL_SCROLL_FACTOR = 48; + @VisibleForTesting static final int DEFAULT_HORIZONTAL_SCROLL_FACTOR = 48; + // This value must match the value in framework's platform_view.dart. // This flag indicates whether the original Android pointer events were batched together. private static final int POINTER_DATA_FLAG_BATCHED = 1; @@ -95,6 +101,9 @@ public class AndroidTouchProcessor { private final Map ongoingPans = new HashMap<>(); + // Only used on api 25 and below to avoid requerying display metrics. + private int cachedVerticalScrollFactor; + /** * Constructs an {@code AndroidTouchProcessor} that will send touch event data to the Flutter * execution context represented by the given {@link FlutterRenderer}. @@ -231,8 +240,8 @@ private void addPointerForIndex( event, pointerIndex, pointerChange, pointerData, transformMatrix, packet, null); } - // TODO(mattcarroll): consider creating a PointerPacket class instead of using a procedure that - // mutates inputs. + // TODO: consider creating a PointerPacket class instead of using a procedure that + // mutates inputs. https://github.com/flutter/flutter/issues/132853 private void addPointerForIndex( MotionEvent event, int pointerIndex, @@ -371,12 +380,11 @@ private void addPointerForIndex( // See android scrollview for insperation. // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/widget/ScrollView.java?q=function:onGenericMotionEvent%20filepath:widget%2FScrollView.java&ss=android%2Fplatform%2Fsuperproject%2Fmain if (signalKind == PointerSignalKind.SCROLL) { - // Default if context is null, chosen to ensure reasonable speed scrolling. - double verticalScaleFactor = 48.0; - double horizontalScaleFactor = 48.0; + double horizontalScaleFactor = DEFAULT_HORIZONTAL_SCROLL_FACTOR; + double verticalScaleFactor = DEFAULT_VERTICAL_SCROLL_FACTOR; if (context != null) { - horizontalScaleFactor = ViewConfiguration.get(context).getScaledHorizontalScrollFactor(); - verticalScaleFactor = ViewConfiguration.get(context).getScaledVerticalScrollFactor(); + horizontalScaleFactor = getHorizontalScrollFactor(context); + verticalScaleFactor = getVerticalScrollFactor(context); } // We flip the sign of the scroll value below because it aligns the pixel value with the // scroll direction in native android. @@ -409,6 +417,44 @@ private void addPointerForIndex( } } + private float getHorizontalScrollFactor(@NonNull Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return ViewConfiguration.get(context).getScaledHorizontalScrollFactor(); + } else { + // Vertical scroll factor is not a typo. This is what View.java does in android. + return getVerticalScrollFactorPre26(context); + } + } + + private float getVerticalScrollFactor(@NonNull Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return getVerticalScrollFactorAbove26(context); + } else { + return getVerticalScrollFactorPre26(context); + } + } + + @TargetApi(26) + private float getVerticalScrollFactorAbove26(@NonNull Context context) { + return ViewConfiguration.get(context).getScaledVerticalScrollFactor(); + } + + // See + // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/view/View.java?q=function:getVerticalScrollFactor%20filepath:android%2Fview%2FView.java&ss=android%2Fplatform%2Fsuperproject%2Fmain + private int getVerticalScrollFactorPre26(@NonNull Context context) { + if (cachedVerticalScrollFactor == 0) { + TypedValue outValue = new TypedValue(); + if (!context + .getTheme() + .resolveAttribute(android.R.attr.listPreferredItemHeight, outValue, true)) { + return DEFAULT_VERTICAL_SCROLL_FACTOR; + } + cachedVerticalScrollFactor = + (int) outValue.getDimension(context.getResources().getDisplayMetrics()); + } + return cachedVerticalScrollFactor; + } + @PointerChange private int getPointerChangeForAction(int maskedAction) { // Primary pointer: 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 5f7ef456ba5c6..68e1e7f167ff6 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/AndroidTouchProcessorTest.java @@ -9,6 +9,7 @@ import android.annotation.TargetApi; import android.content.Context; +import android.os.Build; import android.view.InputDevice; import android.view.MotionEvent; import android.view.ViewConfiguration; @@ -350,7 +351,8 @@ public void unexpectedMaskedAction() { } @Test - public void scrollWheel() { + @Config(minSdk = Build.VERSION_CODES.O) + public void scrollWheelAbove26() { // Pointer id must be zero to match actionIndex in mocked event. final int pointerId = 0; MotionEventMocker mocker = @@ -396,6 +398,64 @@ public void scrollWheel() { inOrder.verifyNoMoreInteractions(); } + @Test + @Config(sdk = {Build.VERSION_CODES.N_MR1}) + public void scrollWheelBelow26() { + // Pointer id must be zero to match actionIndex in mocked event. + final int pointerId = 0; + MotionEventMocker mocker = + new MotionEventMocker( + pointerId, InputDevice.SOURCE_CLASS_POINTER, MotionEvent.TOOL_TYPE_MOUSE); + final float horizontalScrollValue = -1f; + final float verticalScrollValue = .5f; + final Context context = ApplicationProvider.getApplicationContext(); + + final MotionEvent event = + mocker.mockEvent( + MotionEvent.ACTION_SCROLL, + 0.0f, + 0.0f, + 1, + horizontalScrollValue, + verticalScrollValue, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f); + boolean handled = touchProcessor.onGenericMotionEvent(event, context); + assertEquals(true, handled); + + InOrder inOrder = inOrder(mockRenderer); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + ByteBuffer packet = packetCaptor.getValue(); + + // Magic number from roboletric's theme. + final double magicScrollFactor = 64; + assertEquals(-horizontalScrollValue * magicScrollFactor, readScrollDeltaX(packet)); + assertEquals(-verticalScrollValue * magicScrollFactor, readScrollDeltaY(packet)); + verify(event).getAxisValue(MotionEvent.AXIS_HSCROLL, pointerId); + verify(event).getAxisValue(MotionEvent.AXIS_VSCROLL, pointerId); + + // Trigger default values. + touchProcessor.onGenericMotionEvent(event, null); + inOrder + .verify(mockRenderer) + .dispatchPointerDataPacket(packetCaptor.capture(), packetSizeCaptor.capture()); + packet = packetCaptor.getValue(); + + assertEquals( + (double) -horizontalScrollValue * AndroidTouchProcessor.DEFAULT_HORIZONTAL_SCROLL_FACTOR, + readScrollDeltaX(packet)); + assertEquals( + (double) -verticalScrollValue * AndroidTouchProcessor.DEFAULT_VERTICAL_SCROLL_FACTOR, + readScrollDeltaY(packet)); + + inOrder.verifyNoMoreInteractions(); + } + @Test public void timeStamp() { final int pointerId = 0; From ae7439ffb4c1d33e12cf5bfc5330bed2fb93005a Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Mon, 21 Aug 2023 13:56:44 -0500 Subject: [PATCH 25/25] Update shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java --- .../io/flutter/embedding/android/AndroidTouchProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index 5a4a9c1b9da40..f3a75a991b309 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -377,7 +377,7 @@ private void addPointerForIndex( packet.putLong(pointerData); // platformData - // See android scrollview for insperation. + // See android scrollview for inspiration. // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/widget/ScrollView.java?q=function:onGenericMotionEvent%20filepath:widget%2FScrollView.java&ss=android%2Fplatform%2Fsuperproject%2Fmain if (signalKind == PointerSignalKind.SCROLL) { double horizontalScaleFactor = DEFAULT_HORIZONTAL_SCROLL_FACTOR;