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

Commit b15a076

Browse files
Patrick Sosinskiiskakaushik
Patrick Sosinski
andauthored
[android] Childview will process its motion events (#19662) (#19776)
Co-authored-by: Kaushik Iska <[email protected]>
1 parent d480851 commit b15a076

File tree

7 files changed

+99
-26
lines changed

7 files changed

+99
-26
lines changed

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

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.flutter.embedding.android;
22

3+
import android.graphics.Matrix;
34
import android.os.Build;
45
import android.view.InputDevice;
56
import android.view.MotionEvent;
@@ -69,19 +70,28 @@ public class AndroidTouchProcessor {
6970

7071
private static final int _POINTER_BUTTON_PRIMARY = 1;
7172

73+
private static final Matrix IDENTITY_TRANSFORM = new Matrix();
74+
75+
private final boolean trackMotionEvents;
76+
7277
/**
7378
* Constructs an {@code AndroidTouchProcessor} that will send touch event data to the Flutter
7479
* execution context represented by the given {@link FlutterRenderer}.
7580
*/
7681
// TODO(mattcarroll): consider moving packet behavior to a FlutterInteractionSurface instead of
7782
// FlutterRenderer
78-
public AndroidTouchProcessor(@NonNull FlutterRenderer renderer) {
83+
public AndroidTouchProcessor(@NonNull FlutterRenderer renderer, boolean trackMotionEvents) {
7984
this.renderer = renderer;
8085
this.motionEventTracker = MotionEventTracker.getInstance();
86+
this.trackMotionEvents = trackMotionEvents;
8187
}
8288

83-
/** Sends the given {@link MotionEvent} data to Flutter in a format that Flutter understands. */
8489
public boolean onTouchEvent(@NonNull MotionEvent event) {
90+
return onTouchEvent(event, IDENTITY_TRANSFORM);
91+
}
92+
93+
/** Sends the given {@link MotionEvent} data to Flutter in a format that Flutter understands. */
94+
public boolean onTouchEvent(@NonNull MotionEvent event, Matrix transformMatrix) {
8595
int pointerCount = event.getPointerCount();
8696

8797
// Prepare a data packet of the appropriate size and order.
@@ -99,26 +109,27 @@ public boolean onTouchEvent(@NonNull MotionEvent event) {
99109
|| maskedAction == MotionEvent.ACTION_POINTER_UP);
100110
if (updateForSinglePointer) {
101111
// ACTION_DOWN and ACTION_POINTER_DOWN always apply to a single pointer only.
102-
addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, packet);
112+
addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet);
103113
} else if (updateForMultiplePointers) {
104114
// ACTION_UP and ACTION_POINTER_UP may contain position updates for other pointers.
105115
// We are converting these updates to move events here in order to preserve this data.
106116
// We also mark these events with a flag in order to help the framework reassemble
107117
// the original Android event later, should it need to forward it to a PlatformView.
108118
for (int p = 0; p < pointerCount; p++) {
109119
if (p != event.getActionIndex() && event.getToolType(p) == MotionEvent.TOOL_TYPE_FINGER) {
110-
addPointerForIndex(event, p, PointerChange.MOVE, POINTER_DATA_FLAG_BATCHED, packet);
120+
addPointerForIndex(
121+
event, p, PointerChange.MOVE, POINTER_DATA_FLAG_BATCHED, transformMatrix, packet);
111122
}
112123
}
113124
// It's important that we're sending the UP event last. This allows PlatformView
114125
// to correctly batch everything back into the original Android event if needed.
115-
addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, packet);
126+
addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet);
116127
} else {
117128
// ACTION_MOVE may not actually mean all pointers have moved
118129
// but it's the responsibility of a later part of the system to
119130
// ignore 0-deltas if desired.
120131
for (int p = 0; p < pointerCount; p++) {
121-
addPointerForIndex(event, p, pointerChange, 0, packet);
132+
addPointerForIndex(event, p, pointerChange, 0, transformMatrix, packet);
122133
}
123134
}
124135

@@ -160,7 +171,7 @@ public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
160171
packet.order(ByteOrder.LITTLE_ENDIAN);
161172

162173
// ACTION_HOVER_MOVE always applies to a single pointer only.
163-
addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, packet);
174+
addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, IDENTITY_TRANSFORM, packet);
164175
if (packet.position() % (POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD) != 0) {
165176
throw new AssertionError("Packet position is not on field boundary.");
166177
}
@@ -171,13 +182,21 @@ public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
171182
// TODO(mattcarroll): consider creating a PointerPacket class instead of using a procedure that
172183
// mutates inputs.
173184
private void addPointerForIndex(
174-
MotionEvent event, int pointerIndex, int pointerChange, int pointerData, ByteBuffer packet) {
185+
MotionEvent event,
186+
int pointerIndex,
187+
int pointerChange,
188+
int pointerData,
189+
Matrix transformMatrix,
190+
ByteBuffer packet) {
175191
if (pointerChange == -1) {
176192
return;
177193
}
178194

179-
// TODO (kaushikiska) : pass this in when we have a way to evict framework only events.
180-
// MotionEventTracker.MotionEventId motionEventId = motionEventTracker.track(event);
195+
long motionEventId = 0;
196+
if (trackMotionEvents) {
197+
MotionEventTracker.MotionEventId trackedEvent = motionEventTracker.track(event);
198+
motionEventId = trackedEvent.getId();
199+
}
181200

182201
int pointerKind = getPointerDeviceTypeForToolType(event.getToolType(pointerIndex));
183202

@@ -188,15 +207,21 @@ private void addPointerForIndex(
188207

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

191-
packet.putLong(0); // motionEventId
210+
packet.putLong(motionEventId); // motionEventId
192211
packet.putLong(timeStamp); // time_stamp
193212
packet.putLong(pointerChange); // change
194213
packet.putLong(pointerKind); // kind
195214
packet.putLong(signalKind); // signal_kind
196215
packet.putLong(event.getPointerId(pointerIndex)); // device
197216
packet.putLong(0); // pointer_identifier, will be generated in pointer_data_packet_converter.cc.
198-
packet.putDouble(event.getX(pointerIndex)); // physical_x
199-
packet.putDouble(event.getY(pointerIndex)); // physical_y
217+
218+
// We use this in lieu of using event.getRawX and event.getRawY as we wish to support
219+
// earlier versions than API level 29.
220+
float viewToScreenCoords[] = {event.getX(pointerIndex), event.getY(pointerIndex)};
221+
transformMatrix.mapPoints(viewToScreenCoords);
222+
packet.putDouble(viewToScreenCoords[0]); // physical_x
223+
packet.putDouble(viewToScreenCoords[1]); // physical_y
224+
200225
packet.putDouble(
201226
0.0); // physical_delta_x, will be generated in pointer_data_packet_converter.cc.
202227
packet.putDouble(

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -782,11 +782,6 @@ public AccessibilityNodeProvider getAccessibilityNodeProvider() {
782782
}
783783
}
784784

785-
@Override
786-
public boolean onInterceptTouchEvent(MotionEvent ev) {
787-
return true;
788-
}
789-
790785
// TODO(mattcarroll): Confer with Ian as to why we need this method. Delete if possible, otherwise
791786
// add comments.
792787
private void resetWillNotDraw(boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) {
@@ -857,7 +852,8 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
857852
localizationPlugin = this.flutterEngine.getLocalizationPlugin();
858853
androidKeyProcessor =
859854
new AndroidKeyProcessor(this.flutterEngine.getKeyEventChannel(), textInputPlugin);
860-
androidTouchProcessor = new AndroidTouchProcessor(this.flutterEngine.getRenderer());
855+
androidTouchProcessor =
856+
new AndroidTouchProcessor(this.flutterEngine.getRenderer(), /*trackMotionEvents=*/ false);
861857
accessibilityBridge =
862858
new AccessibilityBridge(
863859
this,
@@ -873,6 +869,9 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
873869
// Connect AccessibilityBridge to the PlatformViewsController within the FlutterEngine.
874870
// This allows platform Views to hook into Flutter's overall accessibility system.
875871
this.flutterEngine.getPlatformViewsController().attachAccessibilityBridge(accessibilityBridge);
872+
this.flutterEngine
873+
.getPlatformViewsController()
874+
.attachToFlutterRenderer(this.flutterEngine.getRenderer());
876875

877876
// Inform the Android framework that it should retrieve a new InputConnection
878877
// now that an engine is attached.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ private MotionEventTracker() {
5050
/** Tracks the event and returns a unique MotionEventId identifying the event. */
5151
public MotionEventId track(MotionEvent event) {
5252
MotionEventId eventId = MotionEventId.createUnique();
53-
eventById.put(eventId.id, event);
53+
eventById.put(eventId.id, MotionEvent.obtain(event));
5454
unusedEvents.add(eventId.id);
5555
return eventId;
5656
}

shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package io.flutter.embedding.engine.mutatorsstack;
22

3+
import android.annotation.SuppressLint;
34
import android.content.Context;
45
import android.graphics.Canvas;
56
import android.graphics.Matrix;
67
import android.graphics.Path;
8+
import android.view.MotionEvent;
79
import android.widget.FrameLayout;
810
import androidx.annotation.NonNull;
11+
import io.flutter.embedding.android.AndroidTouchProcessor;
912

1013
/**
1114
* A view that applies the {@link io.flutter.embedding.engine.mutatorsstack.MutatorsStack} to its
@@ -17,19 +20,26 @@ public class FlutterMutatorView extends FrameLayout {
1720
private int left;
1821
private int top;
1922

23+
private final AndroidTouchProcessor androidTouchProcessor;
24+
2025
/**
2126
* Initialize the FlutterMutatorView. Use this to set the screenDensity, which will be used to
2227
* correct the final transform matrix.
2328
*/
24-
public FlutterMutatorView(@NonNull Context context, float screenDensity) {
29+
public FlutterMutatorView(
30+
@NonNull Context context,
31+
float screenDensity,
32+
@NonNull AndroidTouchProcessor androidTouchProcessor) {
2533
super(context, null);
2634
this.screenDensity = screenDensity;
35+
this.androidTouchProcessor = androidTouchProcessor;
2736
}
2837

2938
/** Initialize the FlutterMutatorView. */
3039
public FlutterMutatorView(@NonNull Context context) {
3140
super(context, null);
3241
this.screenDensity = 1;
42+
this.androidTouchProcessor = null;
3343
}
3444

3545
/**
@@ -71,7 +81,14 @@ public void dispatchDraw(Canvas canvas) {
7181
// Apply all the transforms on the child canvas.
7282
canvas.save();
7383

84+
canvas.concat(getPlatformViewMatrix());
85+
super.dispatchDraw(canvas);
86+
canvas.restore();
87+
}
88+
89+
private Matrix getPlatformViewMatrix() {
7490
Matrix finalMatrix = new Matrix(mutatorsStack.getFinalMatrix());
91+
7592
// Reverse scale based on screen scale.
7693
//
7794
// The Android frame is set based on the logical resolution instead of physical.
@@ -80,6 +97,7 @@ public void dispatchDraw(Canvas canvas) {
8097
// 500 points in Android. And until this point, we did all the calculation based on the flow
8198
// resolution. So we need to scale down to match Android's logical resolution.
8299
finalMatrix.preScale(1 / screenDensity, 1 / screenDensity);
100+
83101
// Reverse the current offset.
84102
//
85103
// The frame of this view includes the final offset of the bounding rect.
@@ -88,8 +106,27 @@ public void dispatchDraw(Canvas canvas) {
88106
// all the clipping paths
89107
finalMatrix.postTranslate(-left, -top);
90108

91-
canvas.concat(finalMatrix);
92-
super.dispatchDraw(canvas);
93-
canvas.restore();
109+
return finalMatrix;
110+
}
111+
112+
/** Intercept the events here and do not propagate them to the child platform views. */
113+
@Override
114+
public boolean onInterceptTouchEvent(MotionEvent event) {
115+
return true;
116+
}
117+
118+
@Override
119+
@SuppressLint("ClickableViewAccessibility")
120+
public boolean onTouchEvent(MotionEvent event) {
121+
if (androidTouchProcessor == null) {
122+
return super.onTouchEvent(event);
123+
}
124+
125+
// Mutator view itself doesn't rotate, scale, skew, etc.
126+
// we only need to account for translation.
127+
Matrix screenMatrix = new Matrix();
128+
screenMatrix.postTranslate(left, top);
129+
130+
return androidTouchProcessor.onTouchEvent(event, screenMatrix);
94131
}
95132
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
import androidx.annotation.NonNull;
2020
import androidx.annotation.UiThread;
2121
import androidx.annotation.VisibleForTesting;
22+
import io.flutter.embedding.android.AndroidTouchProcessor;
2223
import io.flutter.embedding.android.FlutterImageView;
2324
import io.flutter.embedding.android.FlutterView;
2425
import io.flutter.embedding.android.MotionEventTracker;
2526
import io.flutter.embedding.engine.FlutterOverlaySurface;
2627
import io.flutter.embedding.engine.dart.DartExecutor;
2728
import io.flutter.embedding.engine.mutatorsstack.*;
29+
import io.flutter.embedding.engine.renderer.FlutterRenderer;
2830
import io.flutter.embedding.engine.systemchannels.PlatformViewsChannel;
2931
import io.flutter.plugin.editing.TextInputPlugin;
3032
import io.flutter.view.AccessibilityBridge;
@@ -45,6 +47,8 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
4547

4648
private final PlatformViewRegistryImpl registry;
4749

50+
private AndroidTouchProcessor androidTouchProcessor;
51+
4852
// The context of the Activity or Fragment hosting the render target for the Flutter engine.
4953
private Context context;
5054

@@ -673,12 +677,17 @@ private void initializePlatformViewIfNeeded(int viewId) {
673677
platformViews.put(viewId, view);
674678

675679
FlutterMutatorView mutatorView =
676-
new FlutterMutatorView(context, context.getResources().getDisplayMetrics().density);
680+
new FlutterMutatorView(
681+
context, context.getResources().getDisplayMetrics().density, androidTouchProcessor);
677682
mutatorViews.put(viewId, mutatorView);
678683
mutatorView.addView(platformView.getView());
679684
((FlutterView) flutterView).addView(mutatorView);
680685
}
681686

687+
public void attachToFlutterRenderer(FlutterRenderer flutterRenderer) {
688+
androidTouchProcessor = new AndroidTouchProcessor(flutterRenderer, /*trackMotionEvents=*/ true);
689+
}
690+
682691
public void onDisplayPlatformView(
683692
int viewId,
684693
int x,

shell/platform/android/io/flutter/view/FlutterView.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,9 @@ public void onPostResume() {
232232
}
233233
mLocalizationPlugin = new LocalizationPlugin(context, localizationChannel);
234234
androidKeyProcessor = new AndroidKeyProcessor(keyEventChannel, mTextInputPlugin);
235-
androidTouchProcessor = new AndroidTouchProcessor(flutterRenderer);
235+
androidTouchProcessor =
236+
new AndroidTouchProcessor(flutterRenderer, /*trackMotionEvents=*/ false);
237+
platformViewsController.attachToFlutterRenderer(flutterRenderer);
236238
mNativeView
237239
.getPluginRegistry()
238240
.getPlatformViewsController()

shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ public void itUsesActionEventTypeFromFrameworkEventForVirtualDisplays() {
138138
assertNotEquals(resolvedEvent.getAction(), original.getAction());
139139
}
140140

141+
@Ignore
141142
@Test
142143
public void itUsesActionEventTypeFromMotionEventForHybridPlatformViews() {
143144
MotionEventTracker motionEventTracker = MotionEventTracker.getInstance();

0 commit comments

Comments
 (0)