[flutter] Resample down/up pointer changes

This makes resampled events work better with code that expect a
fixed interval between touch events.

Test: fx run-host-tests dart_widget_tests
Change-Id: I9f6d85301fe3441f9ef2da0c8b8684ce59e35259
Reviewed-on: https://fuchsia-review.googlesource.com/c/experiences/+/399957
Reviewed-by: Sanjay Chouksey <sanjayc@google.com>
Testability-Review: Sanjay Chouksey <sanjayc@google.com>
Commit-Queue: David Reveman <reveman@google.com>
diff --git a/settings/lib/widgets/lib/src/utils/pointer_events_listener.dart b/settings/lib/widgets/lib/src/utils/pointer_events_listener.dart
index ffab120..8de20e5 100644
--- a/settings/lib/widgets/lib/src/utils/pointer_events_listener.dart
+++ b/settings/lib/widgets/lib/src/utils/pointer_events_listener.dart
@@ -206,7 +206,7 @@
     Timeline.timeSync('PointerEventsListener.dispatchQueuedEvents', () {
       _consumePendingEvents();
       _updateDownPointers();
-      _dispatchMoveChanges();
+      _dispatchResampledChanges();
 
       // Schedule frame callback if down pointers exists or events are
       // still queued. We need the frame callback to determine sample
@@ -245,10 +245,7 @@
             if (!_downPointers.containsKey(event.p.pointerId)) {
               _downPointers[event.p.pointerId] = _DownPointer(event, event);
             }
-
-            // Move changes will be re-sampled instead of dispatched directly.
             _downPointers[event.p.pointerId].next = event;
-            dispatchEvent = false;
             break;
           case PointerEventPhase.up:
           case PointerEventPhase.cancel:
@@ -259,6 +256,9 @@
           case PointerEventPhase.hover:
             break;
         }
+
+        // Touch changes will be re-sampled instead of dispatched directly.
+        dispatchEvent = false;
       }
 
       if (dispatchEvent) {
@@ -307,14 +307,14 @@
     }
   }
 
-  void _dispatchMoveChanges() {
+  void _dispatchResampledChanges() {
     final packets = <ui.PointerData>[];
 
-    // Add move changes for [_downPointers].
+    // Add resampled changes for [_downPointers].
     for (var v in _downPointers.values) {
       var x = v.next.p.x;
       var y = v.next.p.y;
-      var eventTime = v.next.p.eventTime;
+
       // Re-sample if next time stamp is past sample time.
       if (v.next.p.eventTime > _sampleTimeNs &&
           v.next.p.eventTime > v.last.p.eventTime) {
@@ -328,20 +328,33 @@
               (_sampleTimeNs - v.last.p.eventTime).toDouble() / interval;
           x = v.last.p.x + (v.next.p.x - v.last.p.x) * scalar;
           y = v.last.p.y + (v.next.p.y - v.last.p.y) * scalar;
-          eventTime = _sampleTimeNs;
         }, arguments: resampleArguments);
       }
 
-      // Add move change if time stamp is greater than last event.
-      if (eventTime > v.last.p.eventTime) {
-        final event = _clone(v.next.p, PointerEventPhase.move, x, y, eventTime);
+      // Add resampled change if time stamp is greater than last event.
+      if (_sampleTimeNs > v.last.p.eventTime) {
+        final event = _clone(v.next.p, v.next.p.phase, x, y, _sampleTimeNs);
         final eventArguments = <String, int>{
-          'eventTimeUs': eventTime ~/ 1000,
+          'eventTimeUs': _sampleTimeNs ~/ 1000,
         };
-        Timeline.timeSync('PointerEventsListener.addMoveEvent', () {
-          final packet = _getPacket(event);
-          if (packet != null) {
-            packets.add(packet);
+        Timeline.timeSync('PointerEventsListener.addResampledEvents', () {
+          var addMove = true;
+          // Add `down` event if needed.
+          if (_downEvent[v.next.p.pointerId] != true) {
+            final event =
+                _clone(v.next.p, PointerEventPhase.down, x, y, _sampleTimeNs);
+            final packet = _getPacket(event);
+            if (packet != null) {
+              packets.add(packet);
+            }
+            // Skip `move` phase if we already added a `down` event.
+            addMove = false;
+          }
+          if (addMove || v.next.p.phase != PointerEventPhase.move) {
+            final packet = _getPacket(event);
+            if (packet != null) {
+              packets.add(packet);
+            }
           }
         }, arguments: eventArguments, flow: Flow.end(v.last.flow2.id));
 
diff --git a/settings/lib/widgets/test/pointer_events_listener_test.dart b/settings/lib/widgets/test/pointer_events_listener_test.dart
index 4a70d96..51a322f 100644
--- a/settings/lib/widgets/test/pointer_events_listener_test.dart
+++ b/settings/lib/widgets/test/pointer_events_listener_test.dart
@@ -78,22 +78,15 @@
     when(scheduler.currentSystemFrameTimeStamp).thenReturn(frameTime);
     callback(Duration());
 
-    // Two pointer events should have been dispatched.
-    expect(result.length, 2);
-    expect(result[0].timeStamp, Duration(milliseconds: 1));
+    // One pointer event should have been dispatched.
+    expect(result.length, 1);
+    expect(result[0].timeStamp, frameTime + _samplingOffset);
     expect(result[0].change, ui.PointerChange.down);
-    expect(result[0].physicalX, 0.0);
+    expect(result[0].physicalX, 5.0 * ui.window.devicePixelRatio);
     expect(result[0].physicalY, 0.0);
     expect(result[0].physicalDeltaX, 0.0);
     expect(result[0].physicalDeltaY, 0.0);
 
-    expect(result[1].timeStamp, frameTime + _samplingOffset);
-    expect(result[1].change, ui.PointerChange.move);
-    expect(result[1].physicalX, 5.0 * ui.window.devicePixelRatio);
-    expect(result[1].physicalY, 0.0);
-    expect(result[1].physicalDeltaX, 5.0 * ui.window.devicePixelRatio);
-    expect(result[1].physicalDeltaY, 0.0);
-
     // Another frame callback should have been requested.
     callback = verify(scheduler.scheduleFrameCallback(captureThat(isNotNull)))
         .captured
@@ -106,46 +99,35 @@
     callback(Duration());
 
     // Another pointer event should have been dispatched.
-    expect(result.length, 3);
-    expect(result[2].timeStamp, frameTime + _samplingOffset);
-    expect(result[2].change, ui.PointerChange.move);
-    expect(result[2].physicalX, 25.0 * ui.window.devicePixelRatio);
-    expect(result[2].physicalY, 0.0);
-    expect(result[2].physicalDeltaX, 20.0 * ui.window.devicePixelRatio);
-    expect(result[2].physicalDeltaY, 0.0);
-
-    // Another frame callback should have been requested.
-    callback = verify(scheduler.scheduleFrameCallback(captureThat(isNotNull)))
-        .captured
-        .single;
-    verify(scheduler.scheduleFrame());
-    clearInteractions(scheduler);
-
-    frameTime = Duration(milliseconds: 11);
-    when(scheduler.currentSystemFrameTimeStamp).thenReturn(frameTime);
-    callback(Duration());
-
-    // Last pointer event should have been dispatched.
-    expect(result.length, 4);
-    expect(result[3].timeStamp, Duration(milliseconds: 4));
-    expect(result[3].change, ui.PointerChange.up);
-    expect(result[3].physicalX, 30.0 * ui.window.devicePixelRatio);
-    expect(result[3].physicalY, 0.0);
-    expect(result[3].physicalDeltaX, 5.0 * ui.window.devicePixelRatio);
-    expect(result[3].physicalDeltaY, 0.0);
+    expect(result.length, 2);
+    expect(result[1].timeStamp, frameTime + _samplingOffset);
+    expect(result[1].change, ui.PointerChange.up);
+    expect(result[1].physicalX, 25.0 * ui.window.devicePixelRatio);
+    expect(result[1].physicalY, 0.0);
+    expect(result[1].physicalDeltaX, 20.0 * ui.window.devicePixelRatio);
+    expect(result[1].physicalDeltaY, 0.0);
   });
 
   test('bad event time', () {
     const _badEventTimeUs = 9999999;
     final event0 = _createSimulatedPointerEvent(
         PointerEventPhase.down, _badEventTimeUs, 0.0, 0.0);
-    final event1 = _createSimulatedPointerEvent(
-        PointerEventPhase.up, _badEventTimeUs, 0.0, 0.0);
 
     var frameTime = Duration(milliseconds: 6);
     when(scheduler.currentSystemFrameTimeStamp).thenReturn(frameTime);
 
-    pointerEventsListener..onPointerEvent(event0)..onPointerEvent(event1);
+    pointerEventsListener.onPointerEvent(event0);
+
+    // No pointer events should have been dispatched yet.
+    expect(result.isEmpty, true);
+
+    final event1 =
+        _createSimulatedPointerEvent(PointerEventPhase.up, 7000, 0.0, 0.0);
+
+    frameTime = Duration(milliseconds: 7);
+    when(scheduler.currentSystemFrameTimeStamp).thenReturn(frameTime);
+
+    pointerEventsListener.onPointerEvent(event1);
 
     // No pointer events should have been dispatched yet.
     expect(result.isEmpty, true);
@@ -158,18 +140,18 @@
     verify(scheduler.scheduleFrame());
     clearInteractions(scheduler);
 
-    frameTime = Duration(milliseconds: 20);
+    frameTime = Duration(milliseconds: 12);
     when(scheduler.currentSystemFrameTimeStamp).thenReturn(frameTime);
     callback(Duration());
 
-    // Event time stamps should have been ignored and two pointer events
+    // First event time stamp should have been ignored and two pointer events
     // should have been dispatched.
     expect(result.length, 2);
     expect(result[0].change, ui.PointerChange.down);
     expect(result[1].change, ui.PointerChange.up);
   });
 
-  test('Handle a move without a previous pointer down.', () {
+  test('move without a previous pointer down.', () {
     final event0 =
         _createSimulatedPointerEvent(PointerEventPhase.move, 1000, 0.0, 0.0);
     final event1 =
@@ -203,10 +185,10 @@
     when(scheduler.currentSystemFrameTimeStamp).thenReturn(frameTime);
     callback(Duration());
 
-    // Two pointer events should have been dispatched.
+    // One pointer event should have been dispatched.
     expect(result.length, 1);
     expect(result[0].timeStamp, frameTime + _samplingOffset);
-    expect(result[0].change, ui.PointerChange.add);
+    expect(result[0].change, ui.PointerChange.down);
     expect(result[0].physicalX, 5.0 * ui.window.devicePixelRatio);
     expect(result[0].physicalY, 0.0);
     expect(result[0].physicalDeltaX, 0.0);
@@ -226,30 +208,10 @@
     // Another pointer event should have been dispatched.
     expect(result.length, 2);
     expect(result[1].timeStamp, frameTime + _samplingOffset);
-    expect(result[1].change, ui.PointerChange.down);
+    expect(result[1].change, ui.PointerChange.up);
     expect(result[1].physicalX, 25.0 * ui.window.devicePixelRatio);
     expect(result[1].physicalY, 0.0);
     expect(result[1].physicalDeltaX, 20.0 * ui.window.devicePixelRatio);
     expect(result[1].physicalDeltaY, 0.0);
-
-    // Another frame callback should have been requested.
-    callback = verify(scheduler.scheduleFrameCallback(captureThat(isNotNull)))
-        .captured
-        .single;
-    verify(scheduler.scheduleFrame());
-    clearInteractions(scheduler);
-
-    frameTime = Duration(milliseconds: 11);
-    when(scheduler.currentSystemFrameTimeStamp).thenReturn(frameTime);
-    callback(Duration());
-
-    // Last pointer event should have been dispatched.
-    expect(result.length, 3);
-    expect(result[2].timeStamp, Duration(milliseconds: 4));
-    expect(result[2].change, ui.PointerChange.up);
-    expect(result[2].physicalX, 30.0 * ui.window.devicePixelRatio);
-    expect(result[2].physicalY, 0.0);
-    expect(result[2].physicalDeltaX, 5.0 * ui.window.devicePixelRatio);
-    expect(result[2].physicalDeltaY, 0.0);
   });
 }