From 56ce1b2ed5d7b5e323fd1e9266f3fd38333ea51a Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Mon, 4 Mar 2019 17:15:15 -0800 Subject: [PATCH 1/2] Android Embedding PR 16: Add touch support to FlutterView. --- .../engine/android/AndroidTouchProcessor.java | 146 ++++++++++++------ .../embedding/engine/android/FlutterView.java | 6 +- 2 files changed, 104 insertions(+), 48 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/engine/android/AndroidTouchProcessor.java index 9bf0848d68661..b937faec13fd1 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/AndroidTouchProcessor.java @@ -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; @@ -15,26 +17,46 @@ 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; + 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; @@ -57,27 +79,43 @@ 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()); + if (maskedAction == MotionEvent.ACTION_DOWN || maskedAction == MotionEvent.ACTION_POINTER_DOWN) { + // ACTION_DOWN and ACTION_POINTER_DOWN always apply to a single pointer only. + addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, packet); + } else if (maskedAction == MotionEvent.ACTION_UP || maskedAction == MotionEvent.ACTION_POINTER_UP) { + // 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()) { + if (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; + if (packet.position() % (POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD) != 0) { + throw new AssertionError("Packet position is not on field boundary"); + } // Send the packet to Flutter. renderer.dispatchPointerDataPacket(packet, packet.position()); @@ -86,8 +124,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; } @@ -103,9 +146,9 @@ 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 @@ -113,13 +156,19 @@ private void addPointerForIndex(MotionEvent event, int pointerIndex, ByteBuffer 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, 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 { @@ -127,6 +176,8 @@ private void addPointerForIndex(MotionEvent event, int pointerIndex, ByteBuffer 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 @@ -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; } } } diff --git a/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java index aee6a5a95547c..c38b84ba29ca6 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java @@ -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(); @@ -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); } /** @@ -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. From a8792a0a66188b11a03b16a1cabe68dd71e31a7e Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Fri, 8 Mar 2019 17:03:04 -0800 Subject: [PATCH 2/2] PR Updates --- .../engine/android/AndroidTouchProcessor.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/engine/android/AndroidTouchProcessor.java index b937faec13fd1..94c4f7525c51b 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/AndroidTouchProcessor.java @@ -54,6 +54,7 @@ public class AndroidTouchProcessor { } // Must match the unpacking code in hooks.dart. + // 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; @@ -85,19 +86,19 @@ public boolean onTouchEvent(MotionEvent event) { int maskedAction = event.getActionMasked(); int pointerChange = getPointerChangeForAction(event.getActionMasked()); - if (maskedAction == MotionEvent.ACTION_DOWN || maskedAction == MotionEvent.ACTION_POINTER_DOWN) { + 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 (maskedAction == MotionEvent.ACTION_UP || maskedAction == MotionEvent.ACTION_POINTER_UP) { + } 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()) { - if (event.getToolType(p) == MotionEvent.TOOL_TYPE_FINGER) { - addPointerForIndex(event, p, PointerChange.MOVE, POINTER_DATA_FLAG_BATCHED, packet); - } + 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 @@ -113,9 +114,7 @@ public boolean onTouchEvent(MotionEvent event) { } // Verify that the packet is the expected size. - if (packet.position() % (POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD) != 0) { - throw new AssertionError("Packet position is not on field boundary"); - } + assert packet.position() % (POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD) == 0; // Send the packet to Flutter. renderer.dispatchPointerDataPacket(packet, packet.position()); @@ -157,7 +156,8 @@ private void addPointerForIndex( packet.putLong(0); // obscured packet.putDouble(event.getPressure(pointerIndex)); // pressure - double pressureMin = 0.0, pressureMax = 1.0; + double pressureMin = 0.0; + double pressureMax = 1.0; if (event.getDevice() != null) { InputDevice.MotionRange pressureRange = event.getDevice().getMotionRange(MotionEvent.AXIS_PRESSURE); if (pressureRange != null) {