blob: 65b5a7a17afe85ab78a36c53521b30d4a707996b [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.windowdecor;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
import android.graphics.PointF;
import android.view.MotionEvent;
/**
* A detector for touch inputs that differentiates between drag and click inputs. It receives a flow
* of {@link MotionEvent} and generates a new flow of motion events with slop in consideration to
* the event handler. In particular, it always passes down, up and cancel events. It'll pass move
* events only when there is at least one move event that's beyond the slop threshold. For the
* purpose of convenience it also passes all events of other actions.
*
* All touch events must be passed through this class to track a drag event.
*/
class DragDetector {
private final MotionEventHandler mEventHandler;
private final PointF mInputDownPoint = new PointF();
private int mTouchSlop;
private boolean mIsDragEvent;
private int mDragPointerId;
private boolean mResultOfDownAction;
DragDetector(MotionEventHandler eventHandler) {
resetState();
mEventHandler = eventHandler;
}
/**
* The receiver of the {@link MotionEvent} flow.
*
* @return the result returned by {@link #mEventHandler}, or the result when
* {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
*/
boolean onMotionEvent(MotionEvent ev) {
final boolean isTouchScreen =
(ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
if (!isTouchScreen) {
// Only touches generate noisy moves, so mouse/trackpad events don't need to filtered
// to take the slop threshold into consideration.
return mEventHandler.handleMotionEvent(ev);
}
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
mDragPointerId = ev.getPointerId(0);
float rawX = ev.getRawX(0);
float rawY = ev.getRawY(0);
mInputDownPoint.set(rawX, rawY);
mResultOfDownAction = mEventHandler.handleMotionEvent(ev);
return mResultOfDownAction;
}
case ACTION_MOVE: {
if (!mIsDragEvent) {
int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
// Touches generate noisy moves, so only once the move is past the touch
// slop threshold should it be considered a drag.
mIsDragEvent = Math.hypot(dx, dy) > mTouchSlop;
}
// The event handler should only be notified about 'move' events if a drag has been
// detected.
if (mIsDragEvent) {
return mEventHandler.handleMotionEvent(ev);
} else {
return mResultOfDownAction;
}
}
case ACTION_UP:
case ACTION_CANCEL: {
resetState();
return mEventHandler.handleMotionEvent(ev);
}
default:
return mEventHandler.handleMotionEvent(ev);
}
}
void setTouchSlop(int touchSlop) {
mTouchSlop = touchSlop;
}
private void resetState() {
mIsDragEvent = false;
mInputDownPoint.set(0, 0);
mDragPointerId = -1;
mResultOfDownAction = false;
}
interface MotionEventHandler {
boolean handleMotionEvent(MotionEvent ev);
}
}