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

[android] Childview will process its motion events #19662

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.flutter.embedding.android;

import android.graphics.Matrix;
import android.os.Build;
import android.view.InputDevice;
import android.view.MotionEvent;
Expand Down Expand Up @@ -69,19 +70,28 @@ public class AndroidTouchProcessor {

private static final int _POINTER_BUTTON_PRIMARY = 1;

private static final Matrix IDENTITY_TRANSFORM = new Matrix();

private final boolean trackMotionEvents;

/**
* Constructs an {@code AndroidTouchProcessor} that will send touch event data to the Flutter
* execution context represented by the given {@link FlutterRenderer}.
*/
// TODO(mattcarroll): consider moving packet behavior to a FlutterInteractionSurface instead of
// FlutterRenderer
public AndroidTouchProcessor(@NonNull FlutterRenderer renderer) {
public AndroidTouchProcessor(@NonNull FlutterRenderer renderer, boolean trackMotionEvents) {
this.renderer = renderer;
this.motionEventTracker = MotionEventTracker.getInstance();
this.trackMotionEvents = trackMotionEvents;
}

/** Sends the given {@link MotionEvent} data to Flutter in a format that Flutter understands. */
public boolean onTouchEvent(@NonNull MotionEvent event) {
return onTouchEvent(event, IDENTITY_TRANSFORM);
}

/** Sends the given {@link MotionEvent} data to Flutter in a format that Flutter understands. */
public boolean onTouchEvent(@NonNull MotionEvent event, Matrix transformMatrix) {
int pointerCount = event.getPointerCount();

// Prepare a data packet of the appropriate size and order.
Expand All @@ -99,26 +109,27 @@ public boolean onTouchEvent(@NonNull MotionEvent event) {
|| 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, 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.
// We also mark these events with a flag in order to help the framework reassemble
// the original Android event later, should it need to forward it to a PlatformView.
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, packet);
addPointerForIndex(
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, 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
// ignore 0-deltas if desired.
for (int p = 0; p < pointerCount; p++) {
addPointerForIndex(event, p, pointerChange, 0, packet);
addPointerForIndex(event, p, pointerChange, 0, transformMatrix, packet);
}
}

Expand Down Expand Up @@ -160,7 +171,7 @@ 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, packet);
addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, IDENTITY_TRANSFORM, packet);
if (packet.position() % (POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD) != 0) {
throw new AssertionError("Packet position is not on field boundary.");
}
Expand All @@ -171,13 +182,21 @@ public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
// TODO(mattcarroll): consider creating a PointerPacket class instead of using a procedure that
// mutates inputs.
private void addPointerForIndex(
MotionEvent event, int pointerIndex, int pointerChange, int pointerData, ByteBuffer packet) {
MotionEvent event,
int pointerIndex,
int pointerChange,
int pointerData,
Matrix transformMatrix,
ByteBuffer packet) {
if (pointerChange == -1) {
return;
}

// TODO (kaushikiska) : pass this in when we have a way to evict framework only events.
// MotionEventTracker.MotionEventId motionEventId = motionEventTracker.track(event);
long motionEventId = 0;
if (trackMotionEvents) {
MotionEventTracker.MotionEventId trackedEvent = motionEventTracker.track(event);
motionEventId = trackedEvent.getId();
}

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

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

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

packet.putLong(0); // motionEventId
packet.putLong(motionEventId); // motionEventId
packet.putLong(timeStamp); // time_stamp
packet.putLong(pointerChange); // change
packet.putLong(pointerKind); // kind
packet.putLong(signalKind); // signal_kind
packet.putLong(event.getPointerId(pointerIndex)); // device
packet.putLong(0); // pointer_identifier, will be generated in pointer_data_packet_converter.cc.
packet.putDouble(event.getX(pointerIndex)); // physical_x
packet.putDouble(event.getY(pointerIndex)); // physical_y

// We use this in lieu of using event.getRawX and event.getRawY as we wish to support
// earlier versions than API level 29.
float viewToScreenCoords[] = {event.getX(pointerIndex), event.getY(pointerIndex)};
transformMatrix.mapPoints(viewToScreenCoords);
packet.putDouble(viewToScreenCoords[0]); // physical_x
packet.putDouble(viewToScreenCoords[1]); // physical_y

packet.putDouble(
0.0); // physical_delta_x, will be generated in pointer_data_packet_converter.cc.
packet.putDouble(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -782,11 +782,6 @@ public AccessibilityNodeProvider getAccessibilityNodeProvider() {
}
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}

// TODO(mattcarroll): Confer with Ian as to why we need this method. Delete if possible, otherwise
// add comments.
private void resetWillNotDraw(boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) {
Expand Down Expand Up @@ -857,7 +852,8 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
localizationPlugin = this.flutterEngine.getLocalizationPlugin();
androidKeyProcessor =
new AndroidKeyProcessor(this.flutterEngine.getKeyEventChannel(), textInputPlugin);
androidTouchProcessor = new AndroidTouchProcessor(this.flutterEngine.getRenderer());
androidTouchProcessor =
new AndroidTouchProcessor(this.flutterEngine.getRenderer(), /*trackMotionEvents=*/ false);
accessibilityBridge =
new AccessibilityBridge(
this,
Expand All @@ -873,6 +869,9 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
// Connect AccessibilityBridge to the PlatformViewsController within the FlutterEngine.
// This allows platform Views to hook into Flutter's overall accessibility system.
this.flutterEngine.getPlatformViewsController().attachAccessibilityBridge(accessibilityBridge);
this.flutterEngine
.getPlatformViewsController()
.attachToFlutterRenderer(this.flutterEngine.getRenderer());

// Inform the Android framework that it should retrieve a new InputConnection
// now that an engine is attached.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ private MotionEventTracker() {
/** Tracks the event and returns a unique MotionEventId identifying the event. */
public MotionEventId track(MotionEvent event) {
MotionEventId eventId = MotionEventId.createUnique();
eventById.put(eventId.id, event);
eventById.put(eventId.id, MotionEvent.obtain(event));
unusedEvents.add(eventId.id);
return eventId;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package io.flutter.embedding.engine.mutatorsstack;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Path;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import io.flutter.embedding.android.AndroidTouchProcessor;

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

private final AndroidTouchProcessor androidTouchProcessor;

/**
* Initialize the FlutterMutatorView. Use this to set the screenDensity, which will be used to
* correct the final transform matrix.
*/
public FlutterMutatorView(@NonNull Context context, float screenDensity) {
public FlutterMutatorView(
@NonNull Context context,
float screenDensity,
@NonNull AndroidTouchProcessor androidTouchProcessor) {
super(context, null);
this.screenDensity = screenDensity;
this.androidTouchProcessor = androidTouchProcessor;
}

/** Initialize the FlutterMutatorView. */
public FlutterMutatorView(@NonNull Context context) {
super(context, null);
this.screenDensity = 1;
this.androidTouchProcessor = null;
}

/**
Expand Down Expand Up @@ -71,7 +81,14 @@ public void dispatchDraw(Canvas canvas) {
// Apply all the transforms on the child canvas.
canvas.save();

canvas.concat(getPlatformViewMatrix());
super.dispatchDraw(canvas);
canvas.restore();
}

private Matrix getPlatformViewMatrix() {
Matrix finalMatrix = new Matrix(mutatorsStack.getFinalMatrix());

// Reverse scale based on screen scale.
//
// The Android frame is set based on the logical resolution instead of physical.
Expand All @@ -80,6 +97,7 @@ public void dispatchDraw(Canvas canvas) {
// 500 points in Android. And until this point, we did all the calculation based on the flow
// resolution. So we need to scale down to match Android's logical resolution.
finalMatrix.preScale(1 / screenDensity, 1 / screenDensity);

// Reverse the current offset.
//
// The frame of this view includes the final offset of the bounding rect.
Expand All @@ -88,8 +106,27 @@ public void dispatchDraw(Canvas canvas) {
// all the clipping paths
finalMatrix.postTranslate(-left, -top);

canvas.concat(finalMatrix);
super.dispatchDraw(canvas);
canvas.restore();
return finalMatrix;
}

/** Intercept the events here and do not propagate them to the child platform views. */
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return true;
}

@Override
@SuppressLint("ClickableViewAccessibility")
public boolean onTouchEvent(MotionEvent event) {
if (androidTouchProcessor == null) {
return super.onTouchEvent(event);
}

// Mutator view itself doesn't rotate, scale, skew, etc.
// we only need to account for translation.
Matrix screenMatrix = new Matrix();
screenMatrix.postTranslate(left, top);

return androidTouchProcessor.onTouchEvent(event, screenMatrix);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import io.flutter.embedding.android.AndroidTouchProcessor;
import io.flutter.embedding.android.FlutterImageView;
import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.android.MotionEventTracker;
import io.flutter.embedding.engine.FlutterOverlaySurface;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.mutatorsstack.*;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.systemchannels.PlatformViewsChannel;
import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.view.AccessibilityBridge;
Expand All @@ -45,6 +47,8 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega

private final PlatformViewRegistryImpl registry;

private AndroidTouchProcessor androidTouchProcessor;

// The context of the Activity or Fragment hosting the render target for the Flutter engine.
private Context context;

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

FlutterMutatorView mutatorView =
new FlutterMutatorView(context, context.getResources().getDisplayMetrics().density);
new FlutterMutatorView(
context, context.getResources().getDisplayMetrics().density, androidTouchProcessor);
mutatorViews.put(viewId, mutatorView);
mutatorView.addView(platformView.getView());
((FlutterView) flutterView).addView(mutatorView);
}

public void attachToFlutterRenderer(FlutterRenderer flutterRenderer) {
androidTouchProcessor = new AndroidTouchProcessor(flutterRenderer, /*trackMotionEvents=*/ true);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wdyt about making AndroidTouchProcessor a singleton, and pass this trackMotionEvents to onTouchEvent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so for moving trackMotionEvents to AndroidTouchProcessor, the only reason i did not have the additional arg onTouch was because, we also need it for onGenericMotionEvent, so Ideally, we would have a MotionEventTrackingAndroidTouchProcessor as a delegate for AndroidTouchProcessor and have it track all events that go through that, but I added an extra constructor param for now.

}

public void onDisplayPlatformView(
int viewId,
int x,
Expand Down
4 changes: 3 additions & 1 deletion shell/platform/android/io/flutter/view/FlutterView.java
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,9 @@ public void onPostResume() {
}
mLocalizationPlugin = new LocalizationPlugin(context, localizationChannel);
androidKeyProcessor = new AndroidKeyProcessor(keyEventChannel, mTextInputPlugin);
androidTouchProcessor = new AndroidTouchProcessor(flutterRenderer);
androidTouchProcessor =
new AndroidTouchProcessor(flutterRenderer, /*trackMotionEvents=*/ false);
platformViewsController.attachToFlutterRenderer(flutterRenderer);
mNativeView
.getPluginRegistry()
.getPlatformViewsController()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ public void itUsesActionEventTypeFromFrameworkEventForVirtualDisplays() {
assertNotEquals(resolvedEvent.getAction(), original.getAction());
}

@Ignore
@Test
public void itUsesActionEventTypeFromMotionEventForHybridPlatformViews() {
MotionEventTracker motionEventTracker = MotionEventTracker.getInstance();
Expand Down