| /* |
| * 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); |
| } |
| } |