Fix non-vd android platform view input event offsets (flutter/engine#52532)

Fixes https://github.com/flutter/flutter/issues/146570, which tracks a regression from https://github.com/flutter/engine/pull/49268 regarding platform view inputs in some specific cases.

This PR translates the input event location to be the same as the location we calculated before https://github.com/flutter/engine/pull/49268, returning to the previous behavior, while maintaining the input event's verified status (I checked this manually with the `InputManager`).

Tested manually with the reproduction in the linked issue.

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
Gray Mackall 2024-05-14 16:07:53 -07:00 committed by GitHub
parent 98713e6aa6
commit b6bb39ad2d
3 changed files with 102 additions and 37 deletions

View File

@ -501,7 +501,12 @@ public class PlatformViewsChannel {
public final int action;
/** The number of pointers (e.g, fingers) involved in the touch event. */
public final int pointerCount;
/** Properties for each pointer, encoded in a raw format. */
/**
* Properties for each pointer, encoded in a raw format.
* Expected to be formatted as a List[List[Integer]], where each inner list has two items:
* - An id, at index 0, corresponding to {@link android.view.MotionEvent.PointerProperties#id}
* - A tool type, at index 1, corresponding to {@link android.view.MotionEvent.PointerProperties#toolType}.
* */
@NonNull public final Object rawPointerPropertiesList;
/** Coordinates for each pointer, encoded in a raw format. */
@NonNull public final Object rawPointerCoords;

View File

@ -4,8 +4,6 @@
package io.flutter.plugin.platform;
import static android.view.MotionEvent.PointerCoords;
import static android.view.MotionEvent.PointerProperties;
import static io.flutter.Build.API_LEVELS;
import android.annotation.TargetApi;
@ -668,6 +666,25 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
return textureId;
}
/**
* Translates an original touch event to have the same locations as the ones that Flutter
* calculates (because original + flutter's - original = flutter's).
*
* @param originalEvent The saved original input event.
* @param pointerCoords The coordinates that Flutter thinks the touch is happening at.
*/
private static void translateMotionEvent(
MotionEvent originalEvent, PointerCoords[] pointerCoords) {
if (pointerCoords.length < 1) {
return;
}
float xOffset = pointerCoords[0].x - originalEvent.getX();
float yOffset = pointerCoords[0].y - originalEvent.getY();
originalEvent.offsetLocation(xOffset, yOffset);
}
@VisibleForTesting
public MotionEvent toMotionEvent(
float density, PlatformViewsChannel.PlatformViewTouch touch, boolean usingVirtualDiplay) {
@ -675,25 +692,27 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
MotionEventTracker.MotionEventId.from(touch.motionEventId);
MotionEvent trackedEvent = motionEventTracker.pop(motionEventId);
// Pointer coordinates in the tracked events are global to FlutterView
// The framework converts them to be local to a widget, given that
// motion events operate on local coords, we need to replace these in the tracked
// event with their local counterparts.
// Compute this early so it can be used as input to translateNonVirtualDisplayMotionEvent.
PointerCoords[] pointerCoords =
parsePointerCoordsList(touch.rawPointerCoords, density)
.toArray(new PointerCoords[touch.pointerCount]);
if (!usingVirtualDiplay && trackedEvent != null) {
// We have the original event, deliver it as it will pass the verifiable
// We have the original event, deliver it after offsetting as it will pass the verifiable
// input check.
translateMotionEvent(trackedEvent, pointerCoords);
return trackedEvent;
}
// We are in virtual display mode or don't have a reference to the original MotionEvent.
// In this case we manually recreate a MotionEvent to be delivered. This MotionEvent
// will fail the verifiable input check.
// Pointer coordinates in the tracked events are global to FlutterView
// framework converts them to be local to a widget, given that
// motion events operate on local coords, we need to replace these in the tracked
// event with their local counterparts.
PointerProperties[] pointerProperties =
parsePointerPropertiesList(touch.rawPointerPropertiesList)
.toArray(new PointerProperties[touch.pointerCount]);
PointerCoords[] pointerCoords =
parsePointerCoordsList(touch.rawPointerCoords, density)
.toArray(new PointerCoords[touch.pointerCount]);
// TODO (kaushikiska) : warn that we are potentially using an untracked
// event in the platform views.

View File

@ -361,34 +361,48 @@ public class PlatformViewsControllerTest {
assertNotEquals(resolvedEvent.getAction(), frameWorkTouch.action);
}
@Ignore
@Test
public void itUsesActionEventTypeFromMotionEventForHybridPlatformViews() {
MotionEventTracker motionEventTracker = MotionEventTracker.getInstance();
PlatformViewsController platformViewsController = new PlatformViewsController();
MotionEvent original =
MotionEvent.obtain(
100, // downTime
100, // eventTime
1, // action
0, // x
0, // y
0 // metaState
);
// track an event that will later get passed to us from framework
private MotionEvent makePlatformViewTouchAndInvokeToMotionEvent(
PlatformViewsController platformViewsController,
MotionEventTracker motionEventTracker,
MotionEvent original,
boolean usingVirtualDisplays) {
MotionEventTracker.MotionEventId motionEventId = motionEventTracker.track(original);
PlatformViewTouch frameWorkTouch =
// Construct a PlatformViewTouch.rawPointerPropertiesList by doing the inverse of
// PlatformViewsController.parsePointerPropertiesList.
List<List<Integer>> pointerProperties =
Arrays.asList(
Arrays.asList(
original.getPointerId(0),
original.getToolType(0)
)
);
// Construct a PlatformViewTouch.rawPointerCoords by doing the inverse of
// PlatformViewsController.parsePointerCoordsList.
List<List<Double>> pointerCoordinates =
Arrays.asList(
Arrays.asList(
(double) original.getOrientation(),
(double) original.getPressure(),
(double) original.getSize(),
(double) original.getToolMajor(),
(double) original.getToolMinor(),
(double) original.getTouchMajor(),
(double) original.getTouchMinor(),
(double) original.getX(),
(double) original.getY()
)
);
// Make a platform view touch from the motion event.
PlatformViewTouch frameWorkTouchNonVd =
new PlatformViewTouch(
0, // viewId
original.getDownTime(),
original.getEventTime(),
2, // action
original.getAction(),
1, // pointerCount
Arrays.asList(Arrays.asList(0, 0)), // pointer properties
Arrays.asList(Arrays.asList(0., 1., 2., 3., 4., 5., 6., 7., 8.)), // pointer coords
pointerProperties, // pointer properties
pointerCoordinates, // pointer coords
original.getMetaState(),
original.getButtonState(),
original.getXPrecision(),
@ -399,11 +413,38 @@ public class PlatformViewsControllerTest {
original.getFlags(),
motionEventId.getId());
MotionEvent resolvedEvent =
platformViewsController.toMotionEvent(
/*density=*/ 1, frameWorkTouch, /*usingVirtualDisplay=*/ false);
return platformViewsController.toMotionEvent(
1, // density
frameWorkTouchNonVd,
usingVirtualDisplays);
}
assertEquals(resolvedEvent.getAction(), frameWorkTouch.action);
@Test
public void toMotionEvent_returnsSameCoordsForVdAndNonVd() {
MotionEventTracker motionEventTracker = MotionEventTracker.getInstance();
PlatformViewsController platformViewsController = new PlatformViewsController();
MotionEvent original =
MotionEvent.obtain(
10, // downTime
10, // eventTime
261, // action
1, // x
1, // y
0 // metaState
);
MotionEvent resolvedNonVdEvent =
makePlatformViewTouchAndInvokeToMotionEvent(
platformViewsController, motionEventTracker, original, false);
MotionEvent resolvedVdEvent =
makePlatformViewTouchAndInvokeToMotionEvent(
platformViewsController, motionEventTracker, original, true);
assertEquals(resolvedVdEvent.getEventTime(), resolvedNonVdEvent.getEventTime());
assertEquals(resolvedVdEvent.getX(), resolvedNonVdEvent.getX(), 0.001f);
assertEquals(resolvedVdEvent.getY(), resolvedNonVdEvent.getY(), 0.001f);
}
@Test