blob: 3e4e96bec0e43ac65a935ade05e926e69f0d83e2 [file] [log] [blame]
/*
* Copyright (C) 2012 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.launcher3;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import com.android.launcher3.util.TouchUtil;
/**
* Utility class to handle tripper long press or right click on a view with custom timeout and
* stylus event
*/
public class CheckLongPressHelper {
public static final float DEFAULT_LONG_PRESS_TIMEOUT_FACTOR = 0.75f;
private final View mView;
private final View.OnLongClickListener mListener;
private final float mSlop;
private float mLongPressTimeoutFactor = DEFAULT_LONG_PRESS_TIMEOUT_FACTOR;
private boolean mHasPerformedLongPress;
private boolean mIsInMouseRightClick;
private Runnable mPendingCheckForLongPress;
public CheckLongPressHelper(View v) {
this(v, null);
}
public CheckLongPressHelper(View v, View.OnLongClickListener listener) {
mView = v;
mListener = listener;
mSlop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
}
/**
* Handles the touch event on a view
*
* @see View#onTouchEvent(MotionEvent)
*/
public void onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
// Just in case the previous long press hasn't been cleared, we make sure to
// start fresh on touch down.
cancelLongPress();
// Mouse right click should immediately trigger a long press
if (TouchUtil.isMouseRightClickDownOrMove(ev)) {
mIsInMouseRightClick = true;
triggerLongPress();
final Handler handler = mView.getHandler();
if (handler != null) {
// Send an ACTION_UP to end this click gesture to avoid user dragging with
// mouse's right button. Note that we need to call
// {@link Handler#postAtFrontOfQueue()} instead of {@link View#post()} to
// make sure ACTION_UP is sent before any ACTION_MOVE if user is dragging.
final MotionEvent actionUpEvent = MotionEvent.obtain(ev);
actionUpEvent.setAction(MotionEvent.ACTION_UP);
handler.postAtFrontOfQueue(() -> {
mView.getRootView().dispatchTouchEvent(actionUpEvent);
actionUpEvent.recycle();
});
}
break;
}
postCheckForLongPress();
if (isStylusButtonPressed(ev)) {
triggerLongPress();
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
cancelLongPress();
break;
case MotionEvent.ACTION_MOVE:
if (mIsInMouseRightClick
|| !Utilities.pointInView(mView, ev.getX(), ev.getY(), mSlop)) {
cancelLongPress();
} else if (mPendingCheckForLongPress != null && isStylusButtonPressed(ev)) {
// Only trigger long press if it has not been cancelled before
triggerLongPress();
}
break;
}
}
/**
* Overrides the default long press timeout.
*/
public void setLongPressTimeoutFactor(float longPressTimeoutFactor) {
mLongPressTimeoutFactor = longPressTimeoutFactor;
}
private void postCheckForLongPress() {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = this::triggerLongPress;
}
mView.postDelayed(mPendingCheckForLongPress,
(long) (ViewConfiguration.getLongPressTimeout() * mLongPressTimeoutFactor));
}
/**
* Cancels any pending long press and right click
*/
public void cancelLongPress() {
mIsInMouseRightClick = false;
mHasPerformedLongPress = false;
clearCallbacks();
}
/**
* Returns true if long press has been performed in the current touch gesture
*/
public boolean hasPerformedLongPress() {
return mHasPerformedLongPress;
}
private void triggerLongPress() {
if ((mView.getParent() != null)
&& mView.hasWindowFocus()
&& (!mView.isPressed() || mListener != null)
&& !mHasPerformedLongPress) {
boolean handled;
if (mListener != null) {
handled = mListener.onLongClick(mView);
} else {
handled = mView.performLongClick();
}
if (handled) {
mView.setPressed(false);
mHasPerformedLongPress = true;
}
clearCallbacks();
}
}
private void clearCallbacks() {
if (mPendingCheckForLongPress != null) {
mView.removeCallbacks(mPendingCheckForLongPress);
mPendingCheckForLongPress = null;
}
}
/**
* Identifies if the provided {@link MotionEvent} is a stylus with the primary stylus button
* pressed.
*
* @param event The event to check.
* @return Whether a stylus button press occurred.
*/
private static boolean isStylusButtonPressed(MotionEvent event) {
return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
&& event.isButtonPressed(MotionEvent.BUTTON_SECONDARY);
}
}