Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
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,6 +1,8 @@
package io.flutter.embedding.engine.android;

import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.view.InputDevice;
import android.view.MotionEvent;

import java.nio.ByteBuffer;
Expand All @@ -15,26 +17,47 @@
public class AndroidTouchProcessor {

// Must match the PointerChange enum in pointer.dart.
// TODO(mattcarroll): convert these to an IntDef
private static final int POINTER_CHANGE_CANCEL = 0;
private static final int POINTER_CHANGE_ADD = 1;
private static final int POINTER_CHANGE_REMOVE = 2;
private static final int POINTER_CHANGE_HOVER = 3;
private static final int POINTER_CHANGE_DOWN = 4;
private static final int POINTER_CHANGE_MOVE = 5;
private static final int POINTER_CHANGE_UP = 6;
@IntDef({
PointerChange.CANCEL,
PointerChange.ADD,
PointerChange.REMOVE,
PointerChange.HOVER,
PointerChange.DOWN,
PointerChange.MOVE,
PointerChange.UP
})
private @interface PointerChange {
int CANCEL = 0;
int ADD = 1;
int REMOVE = 2;
int HOVER = 3;
int DOWN = 4;
int MOVE = 5;
int UP = 6;
}


// Must match the PointerDeviceKind enum in pointer.dart.
// TODO(mattcarroll): convert these to an IntDef
private static final int POINTER_DEVICE_KIND_TOUCH = 0;
private static final int POINTER_DEVICE_KIND_MOUSE = 1;
private static final int POINTER_DEVICE_KIND_STYLUS = 2;
private static final int POINTER_DEVICE_KIND_INVERTED_STYLUS = 3;
private static final int POINTER_DEVICE_KIND_UNKNOWN = 4;
@IntDef({
PointerDeviceKind.TOUCH,
PointerDeviceKind.MOUSE,
PointerDeviceKind.STYLUS,
PointerDeviceKind.INVERTED_STYLUS,
PointerDeviceKind.UNKNOWN
})
private @interface PointerDeviceKind {
int TOUCH = 0;
int MOUSE = 1;
int STYLUS = 2;
int INVERTED_STYLUS = 3;
int UNKNOWN = 4;
}

// Must match the unpacking code in hooks.dart.
private static final int POINTER_DATA_FIELD_COUNT = 19;
private static final int BYTE_PER_FIELD = 8;
// TODO(mattcarroll): Update with additional fields for scroll wheel support
private static final int POINTER_DATA_FIELD_COUNT = 21;
private static final int BYTES_PER_FIELD = 8;
private static final int POINTER_DATA_FLAG_BATCHED = 1;

@NonNull
private final FlutterRenderer renderer;
Expand All @@ -57,27 +80,41 @@ public boolean onTouchEvent(MotionEvent event) {

// Prepare a data packet of the appropriate size and order.
ByteBuffer packet = ByteBuffer.allocateDirect(
pointerCount * POINTER_DATA_FIELD_COUNT * BYTE_PER_FIELD
pointerCount * POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD
);
packet.order(ByteOrder.LITTLE_ENDIAN);

int maskedAction = event.getActionMasked();
// ACTION_UP, ACTION_POINTER_UP, ACTION_DOWN, and ACTION_POINTER_DOWN
// only apply to a single pointer, other events apply to all pointers.
if (maskedAction == MotionEvent.ACTION_UP || maskedAction == MotionEvent.ACTION_POINTER_UP
|| maskedAction == MotionEvent.ACTION_DOWN || maskedAction == MotionEvent.ACTION_POINTER_DOWN) {
addPointerForIndex(event, event.getActionIndex(), packet);
int pointerChange = getPointerChangeForAction(event.getActionMasked());
boolean updateForSinglePointer = maskedAction == MotionEvent.ACTION_DOWN || maskedAction == MotionEvent.ACTION_POINTER_DOWN;
boolean updateForMultiplePointers = !updateForSinglePointer && (maskedAction == MotionEvent.ACTION_UP || 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);
} 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);
}
}
// 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);
} 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, packet);
addPointerForIndex(event, p, pointerChange, 0, packet);
}
}

// Verify that the packet is the expected size.
assert packet.position() % (POINTER_DATA_FIELD_COUNT * BYTE_PER_FIELD) == 0;
assert packet.position() % (POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD) == 0;

// Send the packet to Flutter.
renderer.dispatchPointerDataPacket(packet, packet.position());
Expand All @@ -86,8 +123,13 @@ public boolean onTouchEvent(MotionEvent event) {
}

// TODO(mattcarroll): consider creating a PointerPacket class instead of using a procedure that mutates inputs.
private void addPointerForIndex(MotionEvent event, int pointerIndex, ByteBuffer packet) {
int pointerChange = getPointerChangeForAction(event.getActionMasked());
private void addPointerForIndex(
MotionEvent event,
int pointerIndex,
int pointerChange,
int pointerData,
ByteBuffer packet
) {
if (pointerChange == -1) {
return;
}
Expand All @@ -103,30 +145,39 @@ private void addPointerForIndex(MotionEvent event, int pointerIndex, ByteBuffer
packet.putDouble(event.getX(pointerIndex)); // physical_x
packet.putDouble(event.getY(pointerIndex)); // physical_y

if (pointerKind == POINTER_DEVICE_KIND_MOUSE) {
if (pointerKind == PointerDeviceKind.MOUSE) {
packet.putLong(event.getButtonState() & 0x1F); // buttons
} else if (pointerKind == POINTER_DEVICE_KIND_STYLUS) {
} else if (pointerKind == PointerDeviceKind.STYLUS) {
packet.putLong((event.getButtonState() >> 4) & 0xF); // buttons
} else {
packet.putLong(0); // buttons
}

packet.putLong(0); // obscured

// TODO(eseidel): Could get the calibrated range if necessary:
// event.getDevice().getMotionRange(MotionEvent.AXIS_PRESSURE)
packet.putDouble(event.getPressure(pointerIndex)); // pressure
packet.putDouble(0.0); // pressure_min
packet.putDouble(1.0); // pressure_max
double pressureMin = 0.0;
double pressureMax = 1.0;
if (event.getDevice() != null) {
InputDevice.MotionRange pressureRange = event.getDevice().getMotionRange(MotionEvent.AXIS_PRESSURE);
if (pressureRange != null) {
pressureMin = pressureRange.getMin();
pressureMax = pressureRange.getMax();
}
}
packet.putDouble(pressureMin); // pressure_min
packet.putDouble(pressureMax); // pressure_max

if (pointerKind == POINTER_DEVICE_KIND_STYLUS) {
if (pointerKind == PointerDeviceKind.STYLUS) {
packet.putDouble(event.getAxisValue(MotionEvent.AXIS_DISTANCE, pointerIndex)); // distance
packet.putDouble(0.0); // distance_max
} else {
packet.putDouble(0.0); // distance
packet.putDouble(0.0); // distance_max
}

packet.putDouble(event.getSize(pointerIndex)); // size

packet.putDouble(event.getToolMajor(pointerIndex)); // radius_major
packet.putDouble(event.getToolMinor(pointerIndex)); // radius_minor

Expand All @@ -135,52 +186,55 @@ private void addPointerForIndex(MotionEvent event, int pointerIndex, ByteBuffer

packet.putDouble(event.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex)); // orientation

if (pointerKind == POINTER_DEVICE_KIND_STYLUS) {
if (pointerKind == PointerDeviceKind.STYLUS) {
packet.putDouble(event.getAxisValue(MotionEvent.AXIS_TILT, pointerIndex)); // tilt
} else {
packet.putDouble(0.0); // tilt
}

packet.putLong(pointerData);
}

@PointerChange
private int getPointerChangeForAction(int maskedAction) {
// Primary pointer:
if (maskedAction == MotionEvent.ACTION_DOWN) {
return POINTER_CHANGE_DOWN;
return PointerChange.DOWN;
}
if (maskedAction == MotionEvent.ACTION_UP) {
return POINTER_CHANGE_UP;
return PointerChange.UP;
}
// Secondary pointer:
if (maskedAction == MotionEvent.ACTION_POINTER_DOWN) {
return POINTER_CHANGE_DOWN;
return PointerChange.DOWN;
}
if (maskedAction == MotionEvent.ACTION_POINTER_UP) {
return POINTER_CHANGE_UP;
return PointerChange.UP;
}
// All pointers:
if (maskedAction == MotionEvent.ACTION_MOVE) {
return POINTER_CHANGE_MOVE;
return PointerChange.MOVE;
}
if (maskedAction == MotionEvent.ACTION_CANCEL) {
return POINTER_CHANGE_CANCEL;
return PointerChange.CANCEL;
}
return -1;
}

// TODO(mattcarroll): introduce IntDef for toolType.
@PointerDeviceKind
private int getPointerDeviceTypeForToolType(int toolType) {
switch (toolType) {
case MotionEvent.TOOL_TYPE_FINGER:
return POINTER_DEVICE_KIND_TOUCH;
return PointerDeviceKind.TOUCH;
case MotionEvent.TOOL_TYPE_STYLUS:
return POINTER_DEVICE_KIND_STYLUS;
return PointerDeviceKind.STYLUS;
case MotionEvent.TOOL_TYPE_MOUSE:
return POINTER_DEVICE_KIND_MOUSE;
return PointerDeviceKind.MOUSE;
case MotionEvent.TOOL_TYPE_ERASER:
return POINTER_DEVICE_KIND_INVERTED_STYLUS;
return PointerDeviceKind.INVERTED_STYLUS;
default:
// MotionEvent.TOOL_TYPE_UNKNOWN will reach here.
return POINTER_DEVICE_KIND_UNKNOWN;
return PointerDeviceKind.UNKNOWN;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ public class FlutterView extends FrameLayout {
private TextInputPlugin textInputPlugin;
@Nullable
private AndroidKeyProcessor androidKeyProcessor;
@Nullable
private AndroidTouchProcessor androidTouchProcessor;

// Directly implemented View behavior that communicates with Flutter.
private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics();
Expand Down Expand Up @@ -308,8 +310,7 @@ public boolean onTouchEvent(MotionEvent event) {
return false;
}

// TODO(mattcarroll): forward event to touch processore when it's merged in.
return false;
return androidTouchProcessor.onTouchEvent(event);
}

/**
Expand Down Expand Up @@ -374,6 +375,7 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
this.flutterEngine.getKeyEventChannel(),
textInputPlugin
);
androidTouchProcessor = new AndroidTouchProcessor(this.flutterEngine.getRenderer());

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