blob: e9cd16c99aeb6c736151b8f85170cc39d08f57a2 [file] [log] [blame]
Winson Chung88f33452012-02-23 15:23:44 -08001/*
2 * Copyright (C) 2012 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
Daniel Sandler325dc232013-06-05 22:57:57 -040017package com.android.launcher3;
Winson Chung88f33452012-02-23 15:23:44 -080018
Fengjiang Li9f90efb2022-12-01 16:53:17 -080019import android.os.Handler;
Sunny Goyal17feee82020-03-24 13:55:15 -070020import android.view.MotionEvent;
Winson Chung88f33452012-02-23 15:23:44 -080021import android.view.View;
Tony2ca999c2018-09-24 17:24:51 -040022import android.view.ViewConfiguration;
Winson Chung88f33452012-02-23 15:23:44 -080023
Fengjiang Lia6a67e32022-12-06 21:53:25 -080024import com.android.launcher3.util.TouchUtil;
25
Sunny Goyal17feee82020-03-24 13:55:15 -070026/**
Fengjiang Li9f90efb2022-12-01 16:53:17 -080027 * Utility class to handle tripper long press or right click on a view with custom timeout and
28 * stylus event
Sunny Goyal17feee82020-03-24 13:55:15 -070029 */
Winson Chung88f33452012-02-23 15:23:44 -080030public class CheckLongPressHelper {
Winson Chung7501adf2015-06-02 11:24:28 -070031
Tony2ca999c2018-09-24 17:24:51 -040032 public static final float DEFAULT_LONG_PRESS_TIMEOUT_FACTOR = 0.75f;
Tony Wickham1bce7fd2016-04-28 17:39:03 -070033
Sunny Goyal17feee82020-03-24 13:55:15 -070034 private final View mView;
35 private final View.OnLongClickListener mListener;
36 private final float mSlop;
Winson Chung88f33452012-02-23 15:23:44 -080037
Sunny Goyal17feee82020-03-24 13:55:15 -070038 private float mLongPressTimeoutFactor = DEFAULT_LONG_PRESS_TIMEOUT_FACTOR;
39
40 private boolean mHasPerformedLongPress;
Fengjiang Li9f90efb2022-12-01 16:53:17 -080041 private boolean mIsInMouseRightClick;
Sunny Goyal17feee82020-03-24 13:55:15 -070042
43 private Runnable mPendingCheckForLongPress;
Winson Chung88f33452012-02-23 15:23:44 -080044
45 public CheckLongPressHelper(View v) {
Sunny Goyal17feee82020-03-24 13:55:15 -070046 this(v, null);
Winson Chung88f33452012-02-23 15:23:44 -080047 }
48
Winson Chung6b276142015-05-13 15:44:26 -070049 public CheckLongPressHelper(View v, View.OnLongClickListener listener) {
50 mView = v;
51 mListener = listener;
Sunny Goyal17feee82020-03-24 13:55:15 -070052 mSlop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
53 }
54
55 /**
56 * Handles the touch event on a view
57 *
58 * @see View#onTouchEvent(MotionEvent)
59 */
60 public void onTouchEvent(MotionEvent ev) {
61 switch (ev.getAction()) {
62 case MotionEvent.ACTION_DOWN: {
63 // Just in case the previous long press hasn't been cleared, we make sure to
64 // start fresh on touch down.
65 cancelLongPress();
66
Fengjiang Li9f90efb2022-12-01 16:53:17 -080067 // Mouse right click should immediately trigger a long press
Fengjiang Lia6a67e32022-12-06 21:53:25 -080068 if (TouchUtil.isMouseRightClickDownOrMove(ev)) {
Fengjiang Li9f90efb2022-12-01 16:53:17 -080069 mIsInMouseRightClick = true;
70 triggerLongPress();
71 final Handler handler = mView.getHandler();
72 if (handler != null) {
73 // Send an ACTION_UP to end this click gesture to avoid user dragging with
74 // mouse's right button. Note that we need to call
75 // {@link Handler#postAtFrontOfQueue()} instead of {@link View#post()} to
76 // make sure ACTION_UP is sent before any ACTION_MOVE if user is dragging.
77 final MotionEvent actionUpEvent = MotionEvent.obtain(ev);
78 actionUpEvent.setAction(MotionEvent.ACTION_UP);
79 handler.postAtFrontOfQueue(() -> {
80 mView.getRootView().dispatchTouchEvent(actionUpEvent);
81 actionUpEvent.recycle();
82 });
83 }
84 break;
85 }
86
Sunny Goyal17feee82020-03-24 13:55:15 -070087 postCheckForLongPress();
88 if (isStylusButtonPressed(ev)) {
89 triggerLongPress();
90 }
91 break;
92 }
93 case MotionEvent.ACTION_CANCEL:
94 case MotionEvent.ACTION_UP:
95 cancelLongPress();
96 break;
97 case MotionEvent.ACTION_MOVE:
Fengjiang Li9f90efb2022-12-01 16:53:17 -080098 if (mIsInMouseRightClick
99 || !Utilities.pointInView(mView, ev.getX(), ev.getY(), mSlop)) {
Sunny Goyal17feee82020-03-24 13:55:15 -0700100 cancelLongPress();
101 } else if (mPendingCheckForLongPress != null && isStylusButtonPressed(ev)) {
102 // Only trigger long press if it has not been cancelled before
103 triggerLongPress();
104 }
105 break;
106 }
Winson Chung6b276142015-05-13 15:44:26 -0700107 }
108
Winson Chung7501adf2015-06-02 11:24:28 -0700109 /**
110 * Overrides the default long press timeout.
111 */
Tony2ca999c2018-09-24 17:24:51 -0400112 public void setLongPressTimeoutFactor(float longPressTimeoutFactor) {
113 mLongPressTimeoutFactor = longPressTimeoutFactor;
Winson Chung7501adf2015-06-02 11:24:28 -0700114 }
115
Sunny Goyal17feee82020-03-24 13:55:15 -0700116 private void postCheckForLongPress() {
Winson Chung88f33452012-02-23 15:23:44 -0800117 mHasPerformedLongPress = false;
118
119 if (mPendingCheckForLongPress == null) {
Sunny Goyal17feee82020-03-24 13:55:15 -0700120 mPendingCheckForLongPress = this::triggerLongPress;
Winson Chung88f33452012-02-23 15:23:44 -0800121 }
Tony2ca999c2018-09-24 17:24:51 -0400122 mView.postDelayed(mPendingCheckForLongPress,
123 (long) (ViewConfiguration.getLongPressTimeout() * mLongPressTimeoutFactor));
Winson Chung88f33452012-02-23 15:23:44 -0800124 }
125
Sunny Goyal17feee82020-03-24 13:55:15 -0700126 /**
Fengjiang Li9f90efb2022-12-01 16:53:17 -0800127 * Cancels any pending long press and right click
Sunny Goyal17feee82020-03-24 13:55:15 -0700128 */
Winson Chung88f33452012-02-23 15:23:44 -0800129 public void cancelLongPress() {
Fengjiang Li9f90efb2022-12-01 16:53:17 -0800130 mIsInMouseRightClick = false;
Winson Chung88f33452012-02-23 15:23:44 -0800131 mHasPerformedLongPress = false;
Sunny Goyal17feee82020-03-24 13:55:15 -0700132 clearCallbacks();
133 }
134
135 /**
136 * Returns true if long press has been performed in the current touch gesture
137 */
138 public boolean hasPerformedLongPress() {
139 return mHasPerformedLongPress;
140 }
141
142 private void triggerLongPress() {
Jon Mirandaa1567d52020-04-15 16:00:01 -0700143 if ((mView.getParent() != null)
144 && mView.hasWindowFocus()
Jon Miranda6fa63472021-01-25 15:30:26 -0500145 && (!mView.isPressed() || mListener != null)
Jon Mirandaa1567d52020-04-15 16:00:01 -0700146 && !mHasPerformedLongPress) {
Sunny Goyal17feee82020-03-24 13:55:15 -0700147 boolean handled;
148 if (mListener != null) {
149 handled = mListener.onLongClick(mView);
150 } else {
151 handled = mView.performLongClick();
152 }
153 if (handled) {
Sunny Goyal9c589542024-12-06 15:27:25 -0800154 // Cancel any default long-press action on the view
155 mView.cancelLongPress();
Sunny Goyal17feee82020-03-24 13:55:15 -0700156 mView.setPressed(false);
157 mHasPerformedLongPress = true;
158 }
159 clearCallbacks();
160 }
161 }
162
163 private void clearCallbacks() {
Winson Chung88f33452012-02-23 15:23:44 -0800164 if (mPendingCheckForLongPress != null) {
Winson Chung88f33452012-02-23 15:23:44 -0800165 mView.removeCallbacks(mPendingCheckForLongPress);
166 mPendingCheckForLongPress = null;
167 }
168 }
169
Sunny Goyal17feee82020-03-24 13:55:15 -0700170
171 /**
172 * Identifies if the provided {@link MotionEvent} is a stylus with the primary stylus button
173 * pressed.
174 *
175 * @param event The event to check.
176 * @return Whether a stylus button press occurred.
177 */
178 private static boolean isStylusButtonPressed(MotionEvent event) {
179 return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
180 && event.isButtonPressed(MotionEvent.BUTTON_SECONDARY);
Winson Chung88f33452012-02-23 15:23:44 -0800181 }
Winson Chung88f33452012-02-23 15:23:44 -0800182}