Loading app/src/main/java/foundation/e/blisslauncher/core/Utilities.java +11 −0 Original line number Diff line number Diff line Loading @@ -209,6 +209,17 @@ public class Utilities { return defaultValue; } /** * Utility method to determine whether the given point, in local coordinates, * is inside the view, where the area of the view is expanded by the slop factor. * This method is called while processing touch-move events to determine if the event * is still within the view. */ public static boolean pointInView(View v, float localX, float localY, float slop) { return localX >= -slop && localY >= -slop && localX < (v.getWidth() + slop) && localY < (v.getHeight() + slop); } /** * Ensures that a value is within given bounds. Specifically: * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound, Loading app/src/main/java/foundation/e/blisslauncher/core/customviews/RoundedWidgetView.java +7 −31 Original line number Diff line number Diff line Loading @@ -86,40 +86,16 @@ public class RoundedWidgetView extends AppWidgetHostView { @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d(TAG, "onInterceptTouchEvent() called with: ev = [" + ev.getAction() + "]"); if (ev.getAction() == MotionEvent.ACTION_DOWN) { mLongPressHelper.cancelLongPress(); mLongPressHelper.onTouchEvent(ev); return mLongPressHelper.hasPerformedLongPress(); } // Consume any touch events for ourselves after longpress is triggered if (mLongPressHelper.hasPerformedLongPress()) { mLongPressHelper.cancelLongPress(); @Override public boolean onTouchEvent(MotionEvent event) { mLongPressHelper.onTouchEvent(event); return true; } // Watch for longpress events at this level to make sure // users can always pick up this widget switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: { mLongPressHelper.postCheckForLongPress(); break; } case MotionEvent.ACTION_UP: mLongPressHelper.cancelLongPress(); break; case MotionEvent.ACTION_CANCEL: mLongPressHelper.cancelLongPress(); break; } // Otherwise continue letting touch events fall through to children return false; } @Override public void cancelLongPress() { super.cancelLongPress(); Loading app/src/main/java/foundation/e/blisslauncher/features/widgets/CheckLongPressHelper.java +107 −21 Original line number Diff line number Diff line Loading @@ -16,54 +16,140 @@ package foundation.e.blisslauncher.features.widgets; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import foundation.e.blisslauncher.BlissLauncher; import foundation.e.blisslauncher.core.Utilities; /** * Utility class to handle tripper long press on a view with custom timeout and stylus event */ public class CheckLongPressHelper { private View mView; 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 CheckForLongPress mPendingCheckForLongPress; private static final String TAG = "CheckLongPressHelper"; private Runnable mPendingCheckForLongPress; class CheckForLongPress implements Runnable { public void run() { if ((mView.getParent() != null) && mView.hasWindowFocus() && !mHasPerformedLongPress) { if (mView.performLongClick()) { mView.setPressed(false); mHasPerformedLongPress = true; 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(); postCheckForLongPress(); if (isStylusButtonPressed(ev)) { triggerLongPress(); } break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: cancelLongPress(); break; case MotionEvent.ACTION_MOVE: if (!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; } } public CheckLongPressHelper(View v) { mView = v; /** * Overrides the default long press timeout. */ public void setLongPressTimeoutFactor(float longPressTimeoutFactor) { mLongPressTimeoutFactor = longPressTimeoutFactor; } public void postCheckForLongPress() { Log.d(TAG, "postCheckForLongPress() called"); private void postCheckForLongPress() { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); mPendingCheckForLongPress = this::triggerLongPress; } mView.postDelayed(mPendingCheckForLongPress, BlissLauncher.getLongPressTimeout()); mView.postDelayed(mPendingCheckForLongPress, (long) (ViewConfiguration.getLongPressTimeout() * mLongPressTimeoutFactor)); } /** * Cancels any pending long press */ public void cancelLongPress() { Log.d(TAG, "cancelLongPress() called"); 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; } } public boolean hasPerformedLongPress() { return mHasPerformedLongPress; /** * 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); } } Loading
app/src/main/java/foundation/e/blisslauncher/core/Utilities.java +11 −0 Original line number Diff line number Diff line Loading @@ -209,6 +209,17 @@ public class Utilities { return defaultValue; } /** * Utility method to determine whether the given point, in local coordinates, * is inside the view, where the area of the view is expanded by the slop factor. * This method is called while processing touch-move events to determine if the event * is still within the view. */ public static boolean pointInView(View v, float localX, float localY, float slop) { return localX >= -slop && localY >= -slop && localX < (v.getWidth() + slop) && localY < (v.getHeight() + slop); } /** * Ensures that a value is within given bounds. Specifically: * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound, Loading
app/src/main/java/foundation/e/blisslauncher/core/customviews/RoundedWidgetView.java +7 −31 Original line number Diff line number Diff line Loading @@ -86,40 +86,16 @@ public class RoundedWidgetView extends AppWidgetHostView { @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d(TAG, "onInterceptTouchEvent() called with: ev = [" + ev.getAction() + "]"); if (ev.getAction() == MotionEvent.ACTION_DOWN) { mLongPressHelper.cancelLongPress(); mLongPressHelper.onTouchEvent(ev); return mLongPressHelper.hasPerformedLongPress(); } // Consume any touch events for ourselves after longpress is triggered if (mLongPressHelper.hasPerformedLongPress()) { mLongPressHelper.cancelLongPress(); @Override public boolean onTouchEvent(MotionEvent event) { mLongPressHelper.onTouchEvent(event); return true; } // Watch for longpress events at this level to make sure // users can always pick up this widget switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: { mLongPressHelper.postCheckForLongPress(); break; } case MotionEvent.ACTION_UP: mLongPressHelper.cancelLongPress(); break; case MotionEvent.ACTION_CANCEL: mLongPressHelper.cancelLongPress(); break; } // Otherwise continue letting touch events fall through to children return false; } @Override public void cancelLongPress() { super.cancelLongPress(); Loading
app/src/main/java/foundation/e/blisslauncher/features/widgets/CheckLongPressHelper.java +107 −21 Original line number Diff line number Diff line Loading @@ -16,54 +16,140 @@ package foundation.e.blisslauncher.features.widgets; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import foundation.e.blisslauncher.BlissLauncher; import foundation.e.blisslauncher.core.Utilities; /** * Utility class to handle tripper long press on a view with custom timeout and stylus event */ public class CheckLongPressHelper { private View mView; 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 CheckForLongPress mPendingCheckForLongPress; private static final String TAG = "CheckLongPressHelper"; private Runnable mPendingCheckForLongPress; class CheckForLongPress implements Runnable { public void run() { if ((mView.getParent() != null) && mView.hasWindowFocus() && !mHasPerformedLongPress) { if (mView.performLongClick()) { mView.setPressed(false); mHasPerformedLongPress = true; 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(); postCheckForLongPress(); if (isStylusButtonPressed(ev)) { triggerLongPress(); } break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: cancelLongPress(); break; case MotionEvent.ACTION_MOVE: if (!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; } } public CheckLongPressHelper(View v) { mView = v; /** * Overrides the default long press timeout. */ public void setLongPressTimeoutFactor(float longPressTimeoutFactor) { mLongPressTimeoutFactor = longPressTimeoutFactor; } public void postCheckForLongPress() { Log.d(TAG, "postCheckForLongPress() called"); private void postCheckForLongPress() { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); mPendingCheckForLongPress = this::triggerLongPress; } mView.postDelayed(mPendingCheckForLongPress, BlissLauncher.getLongPressTimeout()); mView.postDelayed(mPendingCheckForLongPress, (long) (ViewConfiguration.getLongPressTimeout() * mLongPressTimeoutFactor)); } /** * Cancels any pending long press */ public void cancelLongPress() { Log.d(TAG, "cancelLongPress() called"); 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; } } public boolean hasPerformedLongPress() { return mHasPerformedLongPress; /** * 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); } }