Skip to content

Commit

Permalink
Android Embedding PR 16: Add touch support to FlutterView. (flutter#8034
Browse files Browse the repository at this point in the history
)
  • Loading branch information
matthew-carroll authored Mar 9, 2019
1 parent c48774c commit edfc0cf
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 48 deletions.
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 @@ -79,6 +79,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 @@ -313,8 +315,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 @@ -388,6 +389,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

0 comments on commit edfc0cf

Please sign in to comment.