1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wm.shell.windowdecor; 18 19 import static android.view.InputDevice.SOURCE_TOUCHSCREEN; 20 import static android.view.MotionEvent.ACTION_CANCEL; 21 import static android.view.MotionEvent.ACTION_DOWN; 22 import static android.view.MotionEvent.ACTION_MOVE; 23 import static android.view.MotionEvent.ACTION_UP; 24 25 import android.graphics.PointF; 26 import android.view.MotionEvent; 27 import android.view.View; 28 29 import androidx.annotation.Nullable; 30 31 /** 32 * A detector for touch inputs that differentiates between drag and click inputs. It receives a flow 33 * of {@link MotionEvent} and generates a new flow of motion events with slop in consideration to 34 * the event handler. In particular, it always passes down, up and cancel events. It'll pass move 35 * events only when there is at least one move event that's beyond the slop threshold. For the 36 * purpose of convenience it also passes all events of other actions. 37 * 38 * All touch events must be passed through this class to track a drag event. 39 */ 40 class DragDetector { 41 private final MotionEventHandler mEventHandler; 42 43 private final PointF mInputDownPoint = new PointF(); 44 private int mTouchSlop; 45 private boolean mIsDragEvent; 46 private int mDragPointerId; 47 48 private boolean mResultOfDownAction; 49 DragDetector(MotionEventHandler eventHandler)50 DragDetector(MotionEventHandler eventHandler) { 51 resetState(); 52 mEventHandler = eventHandler; 53 } 54 55 /** 56 * The receiver of the {@link MotionEvent} flow. 57 * 58 * @return the result returned by {@link #mEventHandler}, or the result when 59 * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed 60 */ onMotionEvent(MotionEvent ev)61 boolean onMotionEvent(MotionEvent ev) { 62 return onMotionEvent(null /* view */, ev); 63 } 64 65 /** 66 * The receiver of the {@link MotionEvent} flow. 67 * 68 * @return the result returned by {@link #mEventHandler}, or the result when 69 * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed 70 */ onMotionEvent(View v, MotionEvent ev)71 boolean onMotionEvent(View v, MotionEvent ev) { 72 final boolean isTouchScreen = 73 (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN; 74 if (!isTouchScreen) { 75 // Only touches generate noisy moves, so mouse/trackpad events don't need to filtered 76 // to take the slop threshold into consideration. 77 return mEventHandler.handleMotionEvent(v, ev); 78 } 79 switch (ev.getActionMasked()) { 80 case ACTION_DOWN: { 81 mDragPointerId = ev.getPointerId(0); 82 float rawX = ev.getRawX(0); 83 float rawY = ev.getRawY(0); 84 mInputDownPoint.set(rawX, rawY); 85 mResultOfDownAction = mEventHandler.handleMotionEvent(v, ev); 86 return mResultOfDownAction; 87 } 88 case ACTION_MOVE: { 89 if (ev.findPointerIndex(mDragPointerId) == -1) { 90 mDragPointerId = ev.getPointerId(0); 91 } 92 final int dragPointerIndex = ev.findPointerIndex(mDragPointerId); 93 if (!mIsDragEvent) { 94 float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x; 95 float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y; 96 // Touches generate noisy moves, so only once the move is past the touch 97 // slop threshold should it be considered a drag. 98 mIsDragEvent = Math.hypot(dx, dy) > mTouchSlop; 99 } 100 // The event handler should only be notified about 'move' events if a drag has been 101 // detected. 102 if (mIsDragEvent) { 103 return mEventHandler.handleMotionEvent(v, ev); 104 } else { 105 return mResultOfDownAction; 106 } 107 } 108 case ACTION_UP: 109 case ACTION_CANCEL: { 110 resetState(); 111 return mEventHandler.handleMotionEvent(v, ev); 112 } 113 default: 114 return mEventHandler.handleMotionEvent(v, ev); 115 } 116 } 117 setTouchSlop(int touchSlop)118 void setTouchSlop(int touchSlop) { 119 mTouchSlop = touchSlop; 120 } 121 resetState()122 private void resetState() { 123 mIsDragEvent = false; 124 mInputDownPoint.set(0, 0); 125 mDragPointerId = -1; 126 mResultOfDownAction = false; 127 } 128 129 interface MotionEventHandler { handleMotionEvent(@ullable View v, MotionEvent ev)130 boolean handleMotionEvent(@Nullable View v, MotionEvent ev); 131 } 132 } 133