1 /* 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 17 package com.android.launcher3; 18 19 import android.view.MotionEvent; 20 import android.view.View; 21 import android.view.ViewConfiguration; 22 23 /** 24 * Utility class to handle tripper long press on a view with custom timeout and stylus event 25 */ 26 public class CheckLongPressHelper { 27 28 public static final float DEFAULT_LONG_PRESS_TIMEOUT_FACTOR = 0.75f; 29 30 private final View mView; 31 private final View.OnLongClickListener mListener; 32 private final float mSlop; 33 34 private float mLongPressTimeoutFactor = DEFAULT_LONG_PRESS_TIMEOUT_FACTOR; 35 36 private boolean mHasPerformedLongPress; 37 38 private Runnable mPendingCheckForLongPress; 39 CheckLongPressHelper(View v)40 public CheckLongPressHelper(View v) { 41 this(v, null); 42 } 43 CheckLongPressHelper(View v, View.OnLongClickListener listener)44 public CheckLongPressHelper(View v, View.OnLongClickListener listener) { 45 mView = v; 46 mListener = listener; 47 mSlop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop(); 48 } 49 50 /** 51 * Handles the touch event on a view 52 * 53 * @see View#onTouchEvent(MotionEvent) 54 */ onTouchEvent(MotionEvent ev)55 public void onTouchEvent(MotionEvent ev) { 56 switch (ev.getAction()) { 57 case MotionEvent.ACTION_DOWN: { 58 // Just in case the previous long press hasn't been cleared, we make sure to 59 // start fresh on touch down. 60 cancelLongPress(); 61 62 postCheckForLongPress(); 63 if (isStylusButtonPressed(ev)) { 64 triggerLongPress(); 65 } 66 break; 67 } 68 case MotionEvent.ACTION_CANCEL: 69 case MotionEvent.ACTION_UP: 70 cancelLongPress(); 71 break; 72 case MotionEvent.ACTION_MOVE: 73 if (!Utilities.pointInView(mView, ev.getX(), ev.getY(), mSlop)) { 74 cancelLongPress(); 75 } else if (mPendingCheckForLongPress != null && isStylusButtonPressed(ev)) { 76 // Only trigger long press if it has not been cancelled before 77 triggerLongPress(); 78 } 79 break; 80 } 81 } 82 83 /** 84 * Overrides the default long press timeout. 85 */ setLongPressTimeoutFactor(float longPressTimeoutFactor)86 public void setLongPressTimeoutFactor(float longPressTimeoutFactor) { 87 mLongPressTimeoutFactor = longPressTimeoutFactor; 88 } 89 postCheckForLongPress()90 private void postCheckForLongPress() { 91 mHasPerformedLongPress = false; 92 93 if (mPendingCheckForLongPress == null) { 94 mPendingCheckForLongPress = this::triggerLongPress; 95 } 96 mView.postDelayed(mPendingCheckForLongPress, 97 (long) (ViewConfiguration.getLongPressTimeout() * mLongPressTimeoutFactor)); 98 } 99 100 /** 101 * Cancels any pending long press 102 */ cancelLongPress()103 public void cancelLongPress() { 104 mHasPerformedLongPress = false; 105 clearCallbacks(); 106 } 107 108 /** 109 * Returns true if long press has been performed in the current touch gesture 110 */ hasPerformedLongPress()111 public boolean hasPerformedLongPress() { 112 return mHasPerformedLongPress; 113 } 114 triggerLongPress()115 private void triggerLongPress() { 116 if ((mView.getParent() != null) 117 && mView.hasWindowFocus() 118 && (!mView.isPressed() || mListener == null) 119 && !mHasPerformedLongPress) { 120 boolean handled; 121 if (mListener != null) { 122 handled = mListener.onLongClick(mView); 123 } else { 124 handled = mView.performLongClick(); 125 } 126 if (handled) { 127 mView.setPressed(false); 128 mHasPerformedLongPress = true; 129 } 130 clearCallbacks(); 131 } 132 } 133 clearCallbacks()134 private void clearCallbacks() { 135 if (mPendingCheckForLongPress != null) { 136 mView.removeCallbacks(mPendingCheckForLongPress); 137 mPendingCheckForLongPress = null; 138 } 139 } 140 141 142 /** 143 * Identifies if the provided {@link MotionEvent} is a stylus with the primary stylus button 144 * pressed. 145 * 146 * @param event The event to check. 147 * @return Whether a stylus button press occurred. 148 */ isStylusButtonPressed(MotionEvent event)149 private static boolean isStylusButtonPressed(MotionEvent event) { 150 return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS 151 && event.isButtonPressed(MotionEvent.BUTTON_SECONDARY); 152 } 153 } 154