1package io.flutter.embedding.android;
3import static io.flutter.Build.API_LEVELS;
5import android.annotation.TargetApi;
12import android.view.ViewConfiguration;
13import androidx.annotation.IntDef;
14import androidx.annotation.NonNull;
15import androidx.annotation.VisibleForTesting;
16import io.flutter.embedding.engine.renderer.FlutterRenderer;
17import java.nio.ByteBuffer;
18import java.nio.ByteOrder;
19import java.util.HashMap;
24 private static final String TAG =
"AndroidTouchProcessor";
36 PointerChange.PAN_ZOOM_END
46 int PAN_ZOOM_START = 7;
47 int PAN_ZOOM_UPDATE = 8;
58 PointerDeviceKind.UNKNOWN
64 int INVERTED_STYLUS = 3;
75 PointerSignalKind.UNKNOWN
80 int SCROLL_INERTIA_CANCEL = 2;
87 private static final int POINTER_DATA_FIELD_COUNT = 36;
96 private static final int POINTER_DATA_FLAG_BATCHED = 1;
99 private static final int IMPLICIT_VIEW_ID = 0;
104 private static final Matrix IDENTITY_TRANSFORM =
new Matrix();
106 private final boolean trackMotionEvents;
108 private final Map<Integer, float[]> ongoingPans =
new HashMap<>();
111 private int cachedVerticalScrollFactor;
123 this.renderer = renderer;
125 this.trackMotionEvents = trackMotionEvents;
141 int pointerCount =
event.getPointerCount();
147 ByteBuffer.allocateDirect(pointerCount * POINTER_DATA_FIELD_COUNT *
BYTES_PER_FIELD);
148 packet.order(ByteOrder.LITTLE_ENDIAN);
150 int maskedAction =
event.getActionMasked();
151 int pointerChange = getPointerChangeForAction(
event.getActionMasked());
152 boolean updateForSinglePointer =
153 maskedAction == MotionEvent.ACTION_DOWN || maskedAction == MotionEvent.ACTION_POINTER_DOWN;
154 boolean updateForMultiplePointers =
155 !updateForSinglePointer
156 && (maskedAction == MotionEvent.ACTION_UP
157 || maskedAction == MotionEvent.ACTION_POINTER_UP);
158 if (updateForSinglePointer) {
160 addPointerForIndex(
event,
event.getActionIndex(), pointerChange, 0, transformMatrix, packet);
161 }
else if (updateForMultiplePointers) {
166 for (
int p = 0;
p < pointerCount;
p++) {
167 if (
p !=
event.getActionIndex() &&
event.getToolType(
p) == MotionEvent.TOOL_TYPE_FINGER) {
174 addPointerForIndex(
event,
event.getActionIndex(), pointerChange, 0, transformMatrix, packet);
179 for (
int p = 0;
p < pointerCount;
p++) {
180 addPointerForIndex(
event,
p, pointerChange, 0, transformMatrix, packet);
185 if (packet.position() % (POINTER_DATA_FIELD_COUNT *
BYTES_PER_FIELD) != 0) {
186 throw new AssertionError(
"Packet position is not on field boundary");
190 renderer.dispatchPointerDataPacket(packet, packet.position());
209 boolean isPointerEvent =
event.isFromSource(InputDevice.SOURCE_CLASS_POINTER);
210 boolean isMovementEvent =
211 (
event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE
212 ||
event.getActionMasked() == MotionEvent.ACTION_SCROLL);
213 if (isPointerEvent && isMovementEvent) {
219 int pointerChange = getPointerChangeForAction(
event.getActionMasked());
221 ByteBuffer.allocateDirect(
223 packet.order(ByteOrder.LITTLE_ENDIAN);
227 event,
event.getActionIndex(), pointerChange, 0, IDENTITY_TRANSFORM, packet, context);
228 if (packet.position() % (POINTER_DATA_FIELD_COUNT *
BYTES_PER_FIELD) != 0) {
229 throw new AssertionError(
"Packet position is not on field boundary.");
231 renderer.dispatchPointerDataPacket(packet, packet.position());
238 private void addPointerForIndex(
246 event, pointerIndex, pointerChange, pointerData, transformMatrix, packet,
null);
251 private void addPointerForIndex(
259 if (pointerChange == -1) {
265 final int viewId = IMPLICIT_VIEW_ID;
266 final int pointerId =
event.getPointerId(pointerIndex);
268 int pointerKind = getPointerDeviceTypeForToolType(
event.getToolType(pointerIndex));
271 float viewToScreenCoords[] = {
event.getX(pointerIndex),
event.getY(pointerIndex)};
272 transformMatrix.mapPoints(viewToScreenCoords);
274 if (pointerKind == PointerDeviceKind.MOUSE) {
275 buttons =
event.getButtonState() & 0x1F;
277 &&
event.getSource() == InputDevice.SOURCE_MOUSE
278 && pointerChange == PointerChange.DOWN) {
282 ongoingPans.put(pointerId, viewToScreenCoords);
284 }
else if (pointerKind == PointerDeviceKind.STYLUS) {
290 buttons = (
event.getButtonState() >> 4) & 0xF;
295 int panZoomType = -1;
296 boolean isTrackpadPan = ongoingPans.containsKey(pointerId);
298 panZoomType = getPointerChangeForPanZoom(pointerChange);
299 if (panZoomType == -1) {
304 long motionEventId = 0;
305 if (trackMotionEvents) {
306 MotionEventTracker.MotionEventId trackedEvent = motionEventTracker.track(
event);
307 motionEventId = trackedEvent.getId();
311 event.getActionMasked() == MotionEvent.ACTION_SCROLL
312 ? PointerSignalKind.SCROLL
313 : PointerSignalKind.NONE;
315 long timeStamp =
event.getEventTime() * 1000;
317 packet.putLong(motionEventId);
318 packet.putLong(timeStamp);
320 packet.putLong(panZoomType);
321 packet.putLong(PointerDeviceKind.TRACKPAD);
323 packet.putLong(pointerChange);
324 packet.putLong(pointerKind);
326 packet.putLong(signalKind);
327 packet.putLong(pointerId);
331 float[] panStart = ongoingPans.get(pointerId);
332 packet.putDouble(panStart[0]);
333 packet.putDouble(panStart[1]);
335 packet.putDouble(viewToScreenCoords[0]);
336 packet.putDouble(viewToScreenCoords[1]);
344 packet.putLong(buttons);
350 packet.putDouble(
event.getPressure(pointerIndex));
351 double pressureMin = 0.0;
352 double pressureMax = 1.0;
353 if (
event.getDevice() !=
null) {
354 InputDevice.MotionRange pressureRange =
355 event.getDevice().getMotionRange(MotionEvent.AXIS_PRESSURE);
356 if (pressureRange !=
null) {
357 pressureMin = pressureRange.getMin();
358 pressureMax = pressureRange.getMax();
361 packet.putDouble(pressureMin);
362 packet.putDouble(pressureMax);
364 if (pointerKind == PointerDeviceKind.STYLUS) {
365 packet.putDouble(
event.getAxisValue(MotionEvent.AXIS_DISTANCE, pointerIndex));
366 packet.putDouble(0.0);
368 packet.putDouble(0.0);
369 packet.putDouble(0.0);
372 packet.putDouble(
event.getSize(pointerIndex));
374 packet.putDouble(
event.getToolMajor(pointerIndex));
375 packet.putDouble(
event.getToolMinor(pointerIndex));
377 packet.putDouble(0.0);
378 packet.putDouble(0.0);
380 packet.putDouble(
event.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex));
382 if (pointerKind == PointerDeviceKind.STYLUS) {
383 packet.putDouble(
event.getAxisValue(MotionEvent.AXIS_TILT, pointerIndex));
385 packet.putDouble(0.0);
388 packet.putLong(pointerData);
392 if (signalKind == PointerSignalKind.SCROLL) {
395 if (context !=
null) {
396 horizontalScaleFactor = getHorizontalScrollFactor(context);
397 verticalScaleFactor = getVerticalScrollFactor(context);
401 final double horizontalScrollPixels =
402 horizontalScaleFactor * -
event.getAxisValue(MotionEvent.AXIS_HSCROLL, pointerIndex);
403 final double verticalScrollPixels =
404 verticalScaleFactor * -
event.getAxisValue(MotionEvent.AXIS_VSCROLL, pointerIndex);
405 packet.putDouble(horizontalScrollPixels);
406 packet.putDouble(verticalScrollPixels);
408 packet.putDouble(0.0);
409 packet.putDouble(0.0);
413 float[] panStart = ongoingPans.get(pointerId);
414 packet.putDouble(viewToScreenCoords[0] - panStart[0]);
415 packet.putDouble(viewToScreenCoords[1] - panStart[1]);
417 packet.putDouble(0.0);
418 packet.putDouble(0.0);
420 packet.putDouble(0.0);
421 packet.putDouble(0.0);
422 packet.putDouble(1.0);
423 packet.putDouble(0.0);
424 packet.putLong(viewId);
426 if (isTrackpadPan && (panZoomType == PointerChange.PAN_ZOOM_END)) {
427 ongoingPans.remove(pointerId);
431 private float getHorizontalScrollFactor(@NonNull Context context) {
432 if (
Build.VERSION.SDK_INT >= API_LEVELS.API_26) {
433 return ViewConfiguration.get(context).getScaledHorizontalScrollFactor();
436 return getVerticalScrollFactorPre26(context);
440 private float getVerticalScrollFactor(@NonNull Context context) {
441 if (
Build.VERSION.SDK_INT >= API_LEVELS.API_26) {
442 return getVerticalScrollFactorAbove26(context);
444 return getVerticalScrollFactorPre26(context);
448 @TargetApi(API_LEVELS.API_26)
449 private
float getVerticalScrollFactorAbove26(@NonNull Context context) {
450 return ViewConfiguration.get(context).getScaledVerticalScrollFactor();
455 private int getVerticalScrollFactorPre26(@NonNull Context context) {
456 if (cachedVerticalScrollFactor == 0) {
457 TypedValue outValue =
new TypedValue();
460 .resolveAttribute(
android.R.attr.listPreferredItemHeight, outValue,
true)) {
463 cachedVerticalScrollFactor =
464 (
int) outValue.getDimension(context.getResources().getDisplayMetrics());
466 return cachedVerticalScrollFactor;
470 private int getPointerChangeForAction(
int maskedAction) {
472 if (maskedAction == MotionEvent.ACTION_DOWN) {
473 return PointerChange.DOWN;
475 if (maskedAction == MotionEvent.ACTION_UP) {
476 return PointerChange.UP;
479 if (maskedAction == MotionEvent.ACTION_POINTER_DOWN) {
480 return PointerChange.DOWN;
482 if (maskedAction == MotionEvent.ACTION_POINTER_UP) {
483 return PointerChange.UP;
486 if (maskedAction == MotionEvent.ACTION_MOVE) {
487 return PointerChange.MOVE;
489 if (maskedAction == MotionEvent.ACTION_HOVER_MOVE) {
490 return PointerChange.HOVER;
492 if (maskedAction == MotionEvent.ACTION_CANCEL) {
493 return PointerChange.CANCEL;
495 if (maskedAction == MotionEvent.ACTION_SCROLL) {
496 return PointerChange.HOVER;
502 private int getPointerChangeForPanZoom(
int pointerChange) {
503 if (pointerChange == PointerChange.DOWN) {
504 return PointerChange.PAN_ZOOM_START;
505 }
else if (pointerChange == PointerChange.MOVE) {
506 return PointerChange.PAN_ZOOM_UPDATE;
507 }
else if (pointerChange == PointerChange.UP || pointerChange == PointerChange.CANCEL) {
508 return PointerChange.PAN_ZOOM_END;
514 private int getPointerDeviceTypeForToolType(
int toolType) {
516 case MotionEvent.TOOL_TYPE_FINGER:
517 return PointerDeviceKind.TOUCH;
518 case MotionEvent.TOOL_TYPE_STYLUS:
519 return PointerDeviceKind.STYLUS;
520 case MotionEvent.TOOL_TYPE_MOUSE:
521 return PointerDeviceKind.MOUSE;
522 case MotionEvent.TOOL_TYPE_ERASER:
523 return PointerDeviceKind.INVERTED_STYLUS;
526 return PointerDeviceKind.UNKNOWN;
static final int DEFAULT_HORIZONTAL_SCROLL_FACTOR
boolean onTouchEvent(@NonNull MotionEvent event)
boolean onTouchEvent(@NonNull MotionEvent event, @NonNull Matrix transformMatrix)
static final int DEFAULT_VERTICAL_SCROLL_FACTOR
static final int BYTES_PER_FIELD
boolean onGenericMotionEvent(@NonNull MotionEvent event, @NonNull Context context)
AndroidTouchProcessor(@NonNull FlutterRenderer renderer, boolean trackMotionEvents)
static MotionEventTracker getInstance()
int SCROLL_INERTIA_CANCEL
def Build(configs, env, options)
SK_API sk_sp< PrecompileColorFilter > Matrix()