• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package io.flutter.embedding.android;
2 
3 import android.os.Build;
4 import android.support.annotation.IntDef;
5 import android.support.annotation.NonNull;
6 import android.view.InputDevice;
7 import android.view.MotionEvent;
8 
9 import java.nio.ByteBuffer;
10 import java.nio.ByteOrder;
11 
12 import io.flutter.embedding.engine.renderer.FlutterRenderer;
13 
14 /**
15  * Sends touch information from Android to Flutter in a format that Flutter
16  * understands.
17  */
18 public class AndroidTouchProcessor {
19 
20   // Must match the PointerChange enum in pointer.dart.
21   @IntDef({
22       PointerChange.CANCEL,
23       PointerChange.ADD,
24       PointerChange.REMOVE,
25       PointerChange.HOVER,
26       PointerChange.DOWN,
27       PointerChange.MOVE,
28       PointerChange.UP
29   })
30   private @interface PointerChange {
31     int CANCEL = 0;
32     int ADD = 1;
33     int REMOVE = 2;
34     int HOVER = 3;
35     int DOWN = 4;
36     int MOVE = 5;
37     int UP = 6;
38   }
39 
40   // Must match the PointerDeviceKind enum in pointer.dart.
41   @IntDef({
42       PointerDeviceKind.TOUCH,
43       PointerDeviceKind.MOUSE,
44       PointerDeviceKind.STYLUS,
45       PointerDeviceKind.INVERTED_STYLUS,
46       PointerDeviceKind.UNKNOWN
47   })
48   private @interface PointerDeviceKind {
49     int TOUCH = 0;
50     int MOUSE = 1;
51     int STYLUS = 2;
52     int INVERTED_STYLUS = 3;
53     int UNKNOWN = 4;
54   }
55 
56   // Must match the PointerSignalKind enum in pointer.dart.
57   @IntDef({
58       PointerSignalKind.NONE,
59       PointerSignalKind.SCROLL,
60       PointerSignalKind.UNKNOWN
61   })
62   private @interface PointerSignalKind {
63     int NONE = 0;
64     int SCROLL = 1;
65     int UNKNOWN = 2;
66   }
67 
68   // Must match the unpacking code in hooks.dart.
69   private static final int POINTER_DATA_FIELD_COUNT = 24;
70   private static final int BYTES_PER_FIELD = 8;
71 
72   // This value must match the value in framework's platform_view.dart.
73   // This flag indicates whether the original Android pointer events were batched together.
74   private static final int POINTER_DATA_FLAG_BATCHED = 1;
75 
76   @NonNull
77   private final FlutterRenderer renderer;
78 
79   /**
80    * Constructs an {@code AndroidTouchProcessor} that will send touch event data
81    * to the Flutter execution context represented by the given {@link FlutterRenderer}.
82    */
83   // TODO(mattcarroll): consider moving packet behavior to a FlutterInteractionSurface instead of FlutterRenderer
AndroidTouchProcessor(@onNull FlutterRenderer renderer)84   public AndroidTouchProcessor(@NonNull FlutterRenderer renderer) {
85     this.renderer = renderer;
86   }
87 
88   /**
89    * Sends the given {@link MotionEvent} data to Flutter in a format that
90    * Flutter understands.
91    */
onTouchEvent(@onNull MotionEvent event)92   public boolean onTouchEvent(@NonNull MotionEvent event) {
93     int pointerCount = event.getPointerCount();
94 
95     // Prepare a data packet of the appropriate size and order.
96     ByteBuffer packet = ByteBuffer.allocateDirect(
97         pointerCount * POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD
98     );
99     packet.order(ByteOrder.LITTLE_ENDIAN);
100 
101     int maskedAction = event.getActionMasked();
102     int pointerChange = getPointerChangeForAction(event.getActionMasked());
103     boolean updateForSinglePointer = maskedAction == MotionEvent.ACTION_DOWN || maskedAction == MotionEvent.ACTION_POINTER_DOWN;
104     boolean updateForMultiplePointers = !updateForSinglePointer && (maskedAction == MotionEvent.ACTION_UP || maskedAction == MotionEvent.ACTION_POINTER_UP);
105     if (updateForSinglePointer) {
106       // ACTION_DOWN and ACTION_POINTER_DOWN always apply to a single pointer only.
107       addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, packet);
108     } else if (updateForMultiplePointers) {
109       // ACTION_UP and ACTION_POINTER_UP may contain position updates for other pointers.
110       // We are converting these updates to move events here in order to preserve this data.
111       // We also mark these events with a flag in order to help the framework reassemble
112       // the original Android event later, should it need to forward it to a PlatformView.
113       for (int p = 0; p < pointerCount; p++) {
114         if (p != event.getActionIndex() && event.getToolType(p) == MotionEvent.TOOL_TYPE_FINGER) {
115           addPointerForIndex(event, p, PointerChange.MOVE, POINTER_DATA_FLAG_BATCHED, packet);
116         }
117       }
118       // It's important that we're sending the UP event last. This allows PlatformView
119       // to correctly batch everything back into the original Android event if needed.
120       addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, packet);
121     } else {
122       // ACTION_MOVE may not actually mean all pointers have moved
123       // but it's the responsibility of a later part of the system to
124       // ignore 0-deltas if desired.
125       for (int p = 0; p < pointerCount; p++) {
126         addPointerForIndex(event, p, pointerChange, 0, packet);
127       }
128     }
129 
130     // Verify that the packet is the expected size.
131     if (packet.position() % (POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD) != 0) {
132       throw new AssertionError("Packet position is not on field boundary");
133     }
134 
135     // Send the packet to Flutter.
136     renderer.dispatchPointerDataPacket(packet, packet.position());
137 
138     return true;
139   }
140 
141   /**
142    * Sends the given generic {@link MotionEvent} data to Flutter in a format that Flutter
143    * understands.
144    *
145    * Generic motion events include joystick movement, mouse hover, track pad touches, scroll wheel
146    * movements, etc.
147    */
onGenericMotionEvent(@onNull MotionEvent event)148   public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
149     // Method isFromSource is only available in API 18+ (Jelly Bean MR2)
150     // Mouse hover support is not implemented for API < 18.
151     boolean isPointerEvent = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
152         && event.isFromSource(InputDevice.SOURCE_CLASS_POINTER);
153     boolean isMovementEvent = (event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE
154         || event.getActionMasked() == MotionEvent.ACTION_SCROLL);
155     if (!isPointerEvent || !isMovementEvent) {
156       return false;
157     }
158 
159     int pointerChange = getPointerChangeForAction(event.getActionMasked());
160     ByteBuffer packet = ByteBuffer.allocateDirect(
161         event.getPointerCount() * POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD
162     );
163     packet.order(ByteOrder.LITTLE_ENDIAN);
164 
165     // ACTION_HOVER_MOVE always applies to a single pointer only.
166     addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, packet);
167     if (packet.position() % (POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD) != 0) {
168       throw new AssertionError("Packet position is not on field boundary.");
169     }
170     renderer.dispatchPointerDataPacket(packet, packet.position());
171     return true;
172   }
173 
174   // TODO(mattcarroll): consider creating a PointerPacket class instead of using a procedure that mutates inputs.
addPointerForIndex( MotionEvent event, int pointerIndex, int pointerChange, int pointerData, ByteBuffer packet )175   private void addPointerForIndex(
176       MotionEvent event,
177       int pointerIndex,
178       int pointerChange,
179       int pointerData,
180       ByteBuffer packet
181   ) {
182     if (pointerChange == -1) {
183       return;
184     }
185 
186     int pointerKind = getPointerDeviceTypeForToolType(event.getToolType(pointerIndex));
187 
188     int signalKind = event.getActionMasked() == MotionEvent.ACTION_SCROLL
189         ? PointerSignalKind.SCROLL
190         : PointerSignalKind.NONE;
191 
192     long timeStamp = event.getEventTime() * 1000; // Convert from milliseconds to microseconds.
193 
194     packet.putLong(timeStamp); // time_stamp
195     packet.putLong(pointerChange); // change
196     packet.putLong(pointerKind); // kind
197     packet.putLong(signalKind); // signal_kind
198     packet.putLong(event.getPointerId(pointerIndex)); // device
199     packet.putDouble(event.getX(pointerIndex)); // physical_x
200     packet.putDouble(event.getY(pointerIndex)); // physical_y
201 
202     if (pointerKind == PointerDeviceKind.MOUSE) {
203       packet.putLong(event.getButtonState() & 0x1F); // buttons
204     } else if (pointerKind == PointerDeviceKind.STYLUS) {
205       packet.putLong((event.getButtonState() >> 4) & 0xF); // buttons
206     } else {
207       packet.putLong(0); // buttons
208     }
209 
210     packet.putLong(0); // obscured
211 
212     packet.putDouble(event.getPressure(pointerIndex)); // pressure
213     double pressureMin = 0.0;
214     double pressureMax = 1.0;
215     if (event.getDevice() != null) {
216       InputDevice.MotionRange pressureRange = event.getDevice().getMotionRange(MotionEvent.AXIS_PRESSURE);
217       if (pressureRange != null) {
218         pressureMin = pressureRange.getMin();
219         pressureMax = pressureRange.getMax();
220       }
221     }
222     packet.putDouble(pressureMin); // pressure_min
223     packet.putDouble(pressureMax); // pressure_max
224 
225     if (pointerKind == PointerDeviceKind.STYLUS) {
226       packet.putDouble(event.getAxisValue(MotionEvent.AXIS_DISTANCE, pointerIndex)); // distance
227       packet.putDouble(0.0); // distance_max
228     } else {
229       packet.putDouble(0.0); // distance
230       packet.putDouble(0.0); // distance_max
231     }
232 
233     packet.putDouble(event.getSize(pointerIndex)); // size
234 
235     packet.putDouble(event.getToolMajor(pointerIndex)); // radius_major
236     packet.putDouble(event.getToolMinor(pointerIndex)); // radius_minor
237 
238     packet.putDouble(0.0); // radius_min
239     packet.putDouble(0.0); // radius_max
240 
241     packet.putDouble(event.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex)); // orientation
242 
243     if (pointerKind == PointerDeviceKind.STYLUS) {
244       packet.putDouble(event.getAxisValue(MotionEvent.AXIS_TILT, pointerIndex)); // tilt
245     } else {
246       packet.putDouble(0.0); // tilt
247     }
248 
249     packet.putLong(pointerData); // platformData
250 
251     if (signalKind == PointerSignalKind.SCROLL) {
252       packet.putDouble(-event.getAxisValue(MotionEvent.AXIS_HSCROLL)); // scroll_delta_x
253       packet.putDouble(-event.getAxisValue(MotionEvent.AXIS_VSCROLL)); // scroll_delta_y
254     } else {
255       packet.putDouble(0.0);  // scroll_delta_x
256       packet.putDouble(0.0);  // scroll_delta_x
257     }
258   }
259 
260   @PointerChange
getPointerChangeForAction(int maskedAction)261   private int getPointerChangeForAction(int maskedAction) {
262     // Primary pointer:
263     if (maskedAction == MotionEvent.ACTION_DOWN) {
264       return PointerChange.DOWN;
265     }
266     if (maskedAction == MotionEvent.ACTION_UP) {
267       return PointerChange.UP;
268     }
269     // Secondary pointer:
270     if (maskedAction == MotionEvent.ACTION_POINTER_DOWN) {
271       return PointerChange.DOWN;
272     }
273     if (maskedAction == MotionEvent.ACTION_POINTER_UP) {
274       return PointerChange.UP;
275     }
276     // All pointers:
277     if (maskedAction == MotionEvent.ACTION_MOVE) {
278       return PointerChange.MOVE;
279     }
280     if (maskedAction == MotionEvent.ACTION_HOVER_MOVE) {
281       return PointerChange.HOVER;
282     }
283     if (maskedAction == MotionEvent.ACTION_CANCEL) {
284       return PointerChange.CANCEL;
285     }
286     if (maskedAction == MotionEvent.ACTION_SCROLL) {
287       return PointerChange.HOVER;
288     }
289     return -1;
290   }
291 
292   @PointerDeviceKind
getPointerDeviceTypeForToolType(int toolType)293   private int getPointerDeviceTypeForToolType(int toolType) {
294     switch (toolType) {
295       case MotionEvent.TOOL_TYPE_FINGER:
296         return PointerDeviceKind.TOUCH;
297       case MotionEvent.TOOL_TYPE_STYLUS:
298         return PointerDeviceKind.STYLUS;
299       case MotionEvent.TOOL_TYPE_MOUSE:
300         return PointerDeviceKind.MOUSE;
301       case MotionEvent.TOOL_TYPE_ERASER:
302         return PointerDeviceKind.INVERTED_STYLUS;
303       default:
304         // MotionEvent.TOOL_TYPE_UNKNOWN will reach here.
305         return PointerDeviceKind.UNKNOWN;
306     }
307   }
308 }
309