Skip to content

Commit

Permalink
Fix non-vd android platform view input event offsets (#52532)
Browse files Browse the repository at this point in the history
Fixes flutter/flutter#146570, which tracks a regression from #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 #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
  • Loading branch information
gmackall authored May 14, 2024
1 parent 9d631c5 commit 70de32b
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,12 @@ public static class PlatformViewTouch {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -668,32 +666,53 @@ public long configureForTextureLayerComposition(
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) {
MotionEventTracker.MotionEventId motionEventId =
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,34 +361,48 @@ public void itUsesActionEventTypeFromFrameworkEventAsActionChanged() {
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(),
Expand All @@ -399,11 +413,38 @@ public void itUsesActionEventTypeFromMotionEventForHybridPlatformViews() {
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
Expand Down

0 comments on commit 70de32b

Please sign in to comment.