Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 110a579

Browse files
authored
Track motion events for reuse post gesture disambiguation (#19484)
This change makes it so that we track all the motion events encountered by `FlutterView` and all of its subviews in the `MotionEventTracker` class, indexed by a unique `MotionEventId`. This identifier is then passed to the Flutter framework as seen in flutter/flutter#60930. Once the gestures take part in gesture disambiguation and are sent back to the engine, we look-up the original motion event using the `MotionEventId` and dispatch it to the platform. Bug: flutter/flutter#58837
1 parent 5f8e91c commit 110a579

File tree

11 files changed

+165
-38
lines changed

11 files changed

+165
-38
lines changed

ci/licenses_golden/licenses_flutter

+1
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/Flutt
697697
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java
698698
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterTextureView.java
699699
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java
700+
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/MotionEventTracker.java
700701
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/RenderMode.java
701702
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/SplashScreen.java
702703
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/SplashScreenProvider.java

lib/ui/hooks.dart

+3-2
Original file line numberDiff line numberDiff line change
@@ -287,9 +287,9 @@ void _invoke3<A1, A2, A3>(void callback(A1 a1, A2 a2, A3 a3)?, Zone zone, A1 arg
287287
// If this value changes, update the encoding code in the following files:
288288
//
289289
// * pointer_data.cc
290-
// * pointers.dart
290+
// * pointer.dart
291291
// * AndroidTouchProcessor.java
292-
const int _kPointerDataFieldCount = 28;
292+
const int _kPointerDataFieldCount = 29;
293293

294294
PointerDataPacket _unpackPointerDataPacket(ByteData packet) {
295295
const int kStride = Int64List.bytesPerElement;
@@ -300,6 +300,7 @@ PointerDataPacket _unpackPointerDataPacket(ByteData packet) {
300300
for (int i = 0; i < length; ++i) {
301301
int offset = i * _kPointerDataFieldCount;
302302
data.add(PointerData(
303+
embedderId: packet.getInt64(kStride * offset++, _kFakeHostEndian),
303304
timeStamp: Duration(microseconds: packet.getInt64(kStride * offset++, _kFakeHostEndian)),
304305
change: PointerChange.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)],
305306
kind: PointerDeviceKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)],

lib/ui/pointer.dart

+9
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ enum PointerSignalKind {
7272
class PointerData {
7373
/// Creates an object that represents the state of a pointer.
7474
const PointerData({
75+
this.embedderId = 0,
7576
this.timeStamp = Duration.zero,
7677
this.change = PointerChange.cancel,
7778
this.kind = PointerDeviceKind.touch,
@@ -102,6 +103,13 @@ class PointerData {
102103
this.scrollDeltaY = 0.0,
103104
});
104105

106+
/// Unique identifier that ties the [PointerEvent] to embedder event created it.
107+
///
108+
/// No two pointer events can have the same [embedderId]. This is different from
109+
/// [pointerIdentifier] - used for hit-testing, whereas [embedderId] is used to
110+
/// identify the platform event.
111+
final int embedderId;
112+
105113
/// Time of event dispatch, relative to an arbitrary timeline.
106114
final Duration timeStamp;
107115

@@ -263,6 +271,7 @@ class PointerData {
263271
/// Returns a complete textual description of the information in this object.
264272
String toStringFull() {
265273
return '$runtimeType('
274+
'embedderId: $embedderId, '
266275
'timeStamp: $timeStamp, '
267276
'change: $change, '
268277
'kind: $kind, '

lib/ui/window/pointer_data.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
namespace flutter {
1111

1212
// If this value changes, update the pointer data unpacking code in hooks.dart.
13-
static constexpr int kPointerDataFieldCount = 28;
13+
static constexpr int kPointerDataFieldCount = 29;
1414
static constexpr int kBytesPerField = sizeof(int64_t);
1515
// Must match the button constants in events.dart.
1616
enum PointerButtonMouse : int64_t {
@@ -58,6 +58,7 @@ struct alignas(8) PointerData {
5858
kScroll,
5959
};
6060

61+
int64_t embedder_id;
6162
int64_t time_stamp;
6263
Change change;
6364
DeviceKind kind;

lib/web_ui/lib/src/ui/pointer.dart

+39-30
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ enum PointerSignalKind {
7171
class PointerData {
7272
/// Creates an object that represents the state of a pointer.
7373
const PointerData({
74+
this.embedderId = 0,
7475
this.timeStamp = Duration.zero,
7576
this.change = PointerChange.cancel,
7677
this.kind = PointerDeviceKind.touch,
@@ -101,6 +102,13 @@ class PointerData {
101102
this.scrollDeltaY = 0.0,
102103
});
103104

105+
/// Unique identifier that ties the [PointerEvent] to embedder event created it.
106+
///
107+
/// No two pointer events can have the same [embedderId]. This is different from
108+
/// [pointerIdentifier] - used for hit-testing, whereas [embedderId] is used to
109+
/// identify the platform event.
110+
final int embedderId;
111+
104112
/// Time of event dispatch, relative to an arbitrary timeline.
105113
final Duration timeStamp;
106114

@@ -257,46 +265,47 @@ class PointerData {
257265
final double scrollDeltaY;
258266

259267
@override
260-
String toString() => '$runtimeType(x: $physicalX, y: $physicalY)';
268+
String toString() => 'PointerData(x: $physicalX, y: $physicalY)';
261269

262270
/// Returns a complete textual description of the information in this object.
263271
String toStringFull() {
264272
return '$runtimeType('
265-
'timeStamp: $timeStamp, '
266-
'change: $change, '
267-
'kind: $kind, '
268-
'signalKind: $signalKind, '
269-
'device: $device, '
270-
'pointerIdentifier: $pointerIdentifier, '
271-
'physicalX: $physicalX, '
272-
'physicalY: $physicalY, '
273-
'physicalDeltaX: $physicalDeltaX, '
274-
'physicalDeltaY: $physicalDeltaY, '
275-
'buttons: $buttons, '
276-
'synthesized: $synthesized, '
277-
'pressure: $pressure, '
278-
'pressureMin: $pressureMin, '
279-
'pressureMax: $pressureMax, '
280-
'distance: $distance, '
281-
'distanceMax: $distanceMax, '
282-
'size: $size, '
283-
'radiusMajor: $radiusMajor, '
284-
'radiusMinor: $radiusMinor, '
285-
'radiusMin: $radiusMin, '
286-
'radiusMax: $radiusMax, '
287-
'orientation: $orientation, '
288-
'tilt: $tilt, '
289-
'platformData: $platformData, '
290-
'scrollDeltaX: $scrollDeltaX, '
291-
'scrollDeltaY: $scrollDeltaY'
292-
')';
273+
'embedderId: $embedderId, '
274+
'timeStamp: $timeStamp, '
275+
'change: $change, '
276+
'kind: $kind, '
277+
'signalKind: $signalKind, '
278+
'device: $device, '
279+
'pointerIdentifier: $pointerIdentifier, '
280+
'physicalX: $physicalX, '
281+
'physicalY: $physicalY, '
282+
'physicalDeltaX: $physicalDeltaX, '
283+
'physicalDeltaY: $physicalDeltaY, '
284+
'buttons: $buttons, '
285+
'synthesized: $synthesized, '
286+
'pressure: $pressure, '
287+
'pressureMin: $pressureMin, '
288+
'pressureMax: $pressureMax, '
289+
'distance: $distance, '
290+
'distanceMax: $distanceMax, '
291+
'size: $size, '
292+
'radiusMajor: $radiusMajor, '
293+
'radiusMinor: $radiusMinor, '
294+
'radiusMin: $radiusMin, '
295+
'radiusMax: $radiusMax, '
296+
'orientation: $orientation, '
297+
'tilt: $tilt, '
298+
'platformData: $platformData, '
299+
'scrollDeltaX: $scrollDeltaX, '
300+
'scrollDeltaY: $scrollDeltaY'
301+
')';
293302
}
294303
}
295304

296305
/// A sequence of reports about the state of pointers.
297306
class PointerDataPacket {
298307
/// Creates a packet of pointer data reports.
299-
const PointerDataPacket({this.data = const <PointerData>[]}) : assert(data != null); // ignore: unnecessary_null_comparison
308+
const PointerDataPacket({ this.data = const <PointerData>[] }) : assert(data != null); // ignore: unnecessary_null_comparison
300309

301310
/// Data about the individual pointers in this packet.
302311
///

shell/platform/android/BUILD.gn

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ android_java_sources = [
140140
"io/flutter/embedding/android/FlutterSurfaceView.java",
141141
"io/flutter/embedding/android/FlutterTextureView.java",
142142
"io/flutter/embedding/android/FlutterView.java",
143+
"io/flutter/embedding/android/MotionEventTracker.java",
143144
"io/flutter/embedding/android/RenderMode.java",
144145
"io/flutter/embedding/android/SplashScreen.java",
145146
"io/flutter/embedding/android/SplashScreenProvider.java",

shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,15 @@ public class AndroidTouchProcessor {
5757
}
5858

5959
// Must match the unpacking code in hooks.dart.
60-
private static final int POINTER_DATA_FIELD_COUNT = 28;
60+
private static final int POINTER_DATA_FIELD_COUNT = 29;
6161
private static final int BYTES_PER_FIELD = 8;
6262

6363
// This value must match the value in framework's platform_view.dart.
6464
// This flag indicates whether the original Android pointer events were batched together.
6565
private static final int POINTER_DATA_FLAG_BATCHED = 1;
6666

6767
@NonNull private final FlutterRenderer renderer;
68+
@NonNull private final MotionEventTracker motionEventTracker;
6869

6970
private static final int _POINTER_BUTTON_PRIMARY = 1;
7071

@@ -76,6 +77,7 @@ public class AndroidTouchProcessor {
7677
// FlutterRenderer
7778
public AndroidTouchProcessor(@NonNull FlutterRenderer renderer) {
7879
this.renderer = renderer;
80+
this.motionEventTracker = MotionEventTracker.getInstance();
7981
}
8082

8183
/** Sends the given {@link MotionEvent} data to Flutter in a format that Flutter understands. */
@@ -174,6 +176,8 @@ private void addPointerForIndex(
174176
return;
175177
}
176178

179+
MotionEventTracker.MotionEventId motionEventId = motionEventTracker.track(event);
180+
177181
int pointerKind = getPointerDeviceTypeForToolType(event.getToolType(pointerIndex));
178182

179183
int signalKind =
@@ -183,6 +187,7 @@ private void addPointerForIndex(
183187

184188
long timeStamp = event.getEventTime() * 1000; // Convert from milliseconds to microseconds.
185189

190+
packet.putLong(motionEventId.getId());
186191
packet.putLong(timeStamp); // time_stamp
187192
packet.putLong(pointerChange); // change
188193
packet.putLong(pointerKind); // kind
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package io.flutter.embedding.android;
2+
3+
import android.util.LongSparseArray;
4+
import android.view.MotionEvent;
5+
import androidx.annotation.Nullable;
6+
import java.util.PriorityQueue;
7+
import java.util.concurrent.atomic.AtomicLong;
8+
9+
/** Tracks the motion events received by the FlutterView. */
10+
public final class MotionEventTracker {
11+
12+
/** Represents a unique identifier corresponding to a motion event. */
13+
public static class MotionEventId {
14+
private static final AtomicLong ID_COUNTER = new AtomicLong(0);
15+
private final long id;
16+
17+
private MotionEventId(long id) {
18+
this.id = id;
19+
}
20+
21+
public static MotionEventId from(long id) {
22+
return new MotionEventId(id);
23+
}
24+
25+
public static MotionEventId createUnique() {
26+
return MotionEventId.from(ID_COUNTER.incrementAndGet());
27+
}
28+
29+
public long getId() {
30+
return id;
31+
}
32+
}
33+
34+
private final LongSparseArray<MotionEvent> eventById;
35+
private final PriorityQueue<Long> unusedEvents;
36+
private static MotionEventTracker INSTANCE;
37+
38+
public static MotionEventTracker getInstance() {
39+
if (INSTANCE == null) {
40+
INSTANCE = new MotionEventTracker();
41+
}
42+
return INSTANCE;
43+
}
44+
45+
private MotionEventTracker() {
46+
eventById = new LongSparseArray<>();
47+
unusedEvents = new PriorityQueue<>();
48+
}
49+
50+
/** Tracks the event and returns a unique MotionEventId identifying the event. */
51+
public MotionEventId track(MotionEvent event) {
52+
MotionEventId eventId = MotionEventId.createUnique();
53+
eventById.put(eventId.id, event);
54+
unusedEvents.add(eventId.id);
55+
return eventId;
56+
}
57+
58+
/**
59+
* Returns the MotionEvent corresponding to the eventId while discarding all the motion events
60+
* that occured prior to the event represented by the eventId. Returns null if this event was
61+
* popped or discarded.
62+
*/
63+
@Nullable
64+
public MotionEvent pop(MotionEventId eventId) {
65+
// remove all the older events.
66+
while (!unusedEvents.isEmpty() && unusedEvents.peek() < eventId.id) {
67+
eventById.remove(unusedEvents.poll());
68+
}
69+
70+
// remove the current event from the heap if it exists.
71+
if (!unusedEvents.isEmpty() && unusedEvents.peek() == eventId.id) {
72+
unusedEvents.poll();
73+
}
74+
75+
MotionEvent event = eventById.get(eventId.id);
76+
eventById.remove(eventId.id);
77+
return event;
78+
}
79+
}

shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ private void touch(@NonNull MethodCall call, @NonNull MethodChannel.Result resul
166166
(int) args.get(11),
167167
(int) args.get(12),
168168
(int) args.get(13),
169-
(int) args.get(14));
169+
(int) args.get(14),
170+
((Number) args.get(15)).longValue());
170171

171172
try {
172173
handler.onTouch(touch);
@@ -380,6 +381,8 @@ public static class PlatformViewTouch {
380381
public final int source;
381382
/** TODO(mattcarroll): javadoc */
382383
public final int flags;
384+
/** TODO(iskakaushik): javadoc */
385+
public final long motionEventId;
383386

384387
PlatformViewTouch(
385388
int viewId,
@@ -396,7 +399,8 @@ public static class PlatformViewTouch {
396399
int deviceId,
397400
int edgeFlags,
398401
int source,
399-
int flags) {
402+
int flags,
403+
long motionEventId) {
400404
this.viewId = viewId;
401405
this.downTime = downTime;
402406
this.eventTime = eventTime;
@@ -412,6 +416,7 @@ public static class PlatformViewTouch {
412416
this.edgeFlags = edgeFlags;
413417
this.source = source;
414418
this.flags = flags;
419+
this.motionEventId = motionEventId;
415420
}
416421
}
417422
}

shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java

+16-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import androidx.annotation.VisibleForTesting;
2222
import io.flutter.embedding.android.FlutterImageView;
2323
import io.flutter.embedding.android.FlutterView;
24+
import io.flutter.embedding.android.MotionEventTracker;
2425
import io.flutter.embedding.engine.FlutterOverlaySurface;
2526
import io.flutter.embedding.engine.dart.DartExecutor;
2627
import io.flutter.embedding.engine.mutatorsstack.*;
@@ -93,6 +94,9 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
9394
// Platform view IDs that were displayed since the start of the current frame.
9495
private HashSet<Integer> currentFrameUsedPlatformViewIds;
9596

97+
// Used to acquire the original motion events using the motionEventIds.
98+
private final MotionEventTracker motionEventTracker;
99+
96100
private final PlatformViewsChannel.PlatformViewsHandler channelHandler =
97101
new PlatformViewsChannel.PlatformViewsHandler() {
98102

@@ -301,8 +305,16 @@ private void ensureValidAndroidVersion(int minSdkVersion) {
301305
}
302306
};
303307

304-
private static MotionEvent toMotionEvent(
305-
float density, PlatformViewsChannel.PlatformViewTouch touch) {
308+
private MotionEvent toMotionEvent(float density, PlatformViewsChannel.PlatformViewTouch touch) {
309+
MotionEventTracker.MotionEventId motionEventId =
310+
MotionEventTracker.MotionEventId.from(touch.motionEventId);
311+
MotionEvent trackedEvent = motionEventTracker.pop(motionEventId);
312+
if (trackedEvent != null) {
313+
return trackedEvent;
314+
}
315+
316+
// TODO (kaushikiska) : warn that we are potentially using an untracked
317+
// event in the platform views.
306318
PointerProperties[] pointerProperties =
307319
parsePointerPropertiesList(touch.rawPointerPropertiesList)
308320
.toArray(new PointerProperties[touch.pointerCount]);
@@ -339,6 +351,8 @@ public PlatformViewsController() {
339351
platformViewRequests = new SparseArray<>();
340352
platformViews = new SparseArray<>();
341353
mutatorViews = new SparseArray<>();
354+
355+
motionEventTracker = MotionEventTracker.getInstance();
342356
}
343357

344358
/**

shell/platform/embedder/embedder.cc

+2
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,8 @@ FlutterEngineResult FlutterEngineSendPointerEvent(
12251225
for (size_t i = 0; i < events_count; ++i) {
12261226
flutter::PointerData pointer_data;
12271227
pointer_data.Clear();
1228+
// this is currely in use only on android embedding.
1229+
pointer_data.embedder_id = 0;
12281230
pointer_data.time_stamp = SAFE_ACCESS(current, timestamp, 0);
12291231
pointer_data.change = ToPointerDataChange(
12301232
SAFE_ACCESS(current, phase, FlutterPointerPhase::kCancel));

0 commit comments

Comments
 (0)