Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 17feee89 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Merging stylus click logic in longpress helper for better state-management

Bug: 150825081
Change-Id: I7c507c41e67c09bff5a4ad3abc7a7a62fecf910e
parent ba1a2b9b
Loading
Loading
Loading
Loading
+8 −42
Original line number Diff line number Diff line
@@ -39,7 +39,6 @@ import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.widget.TextView;

@@ -109,8 +108,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
    private final int mDisplay;

    private final CheckLongPressHelper mLongPressHelper;
    private final StylusEventHelper mStylusEventHelper;
    private final float mSlop;

    private final boolean mLayoutHorizontal;
    private final int mIconSize;
@@ -137,9 +134,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
    @ViewDebug.ExportedProperty(category = "launcher")
    private boolean mDisableRelayout = false;

    @ViewDebug.ExportedProperty(category = "launcher")
    private final boolean mIgnorePaddingTouch;

    private IconLoadRequest mIconLoadRequest;

    public BubbleTextView(Context context) {
@@ -153,7 +147,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
    public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mActivity = ActivityContext.lookupContext(context);
        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();

        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.BubbleTextView, defStyle, 0);
@@ -166,23 +159,19 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
            setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
            setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
            defaultIconSize = grid.iconSizePx;
            mIgnorePaddingTouch = true;
        } else if (mDisplay == DISPLAY_ALL_APPS) {
            DeviceProfile grid = mActivity.getDeviceProfile();
            setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
            setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
            defaultIconSize = grid.allAppsIconSizePx;
            mIgnorePaddingTouch = true;
        } else if (mDisplay == DISPLAY_FOLDER) {
            DeviceProfile grid = mActivity.getDeviceProfile();
            setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
            setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
            defaultIconSize = grid.folderChildIconSizePx;
            mIgnorePaddingTouch = true;
        } else {
            // widget_selection or shortcut_popup
            defaultIconSize = mActivity.getDeviceProfile().iconSizePx;
            mIgnorePaddingTouch = false;
        }

        mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false);
@@ -192,7 +181,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
        a.recycle();

        mLongPressHelper = new CheckLongPressHelper(this);
        mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);

        mDotParams = new DotRenderer.DrawParams();

@@ -333,42 +321,21 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // ignore events if they happen in padding area
        if (event.getAction() == MotionEvent.ACTION_DOWN && mIgnorePaddingTouch
        if (event.getAction() == MotionEvent.ACTION_DOWN
                && (event.getY() < getPaddingTop()
                || event.getX() < getPaddingLeft()
                || event.getY() > getHeight() - getPaddingBottom()
                || event.getX() > getWidth() - getPaddingRight())) {
            return false;
        }

        // Call the superclass onTouchEvent first, because sometimes it changes the state to
        // isPressed() on an ACTION_UP
        boolean result = super.onTouchEvent(event);

        // Check for a stylus button press, if it occurs cancel any long press checks.
        if (mStylusEventHelper.onMotionEvent(event)) {
            mLongPressHelper.cancelLongPress();
            result = true;
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // If we're in a stylus button press, don't check for long press.
                if (!mStylusEventHelper.inStylusButtonPressed()) {
                    mLongPressHelper.postCheckForLongPress();
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                mLongPressHelper.cancelLongPress();
                break;
            case MotionEvent.ACTION_MOVE:
                if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
                    mLongPressHelper.cancelLongPress();
                }
                break;
        if (isLongClickable()) {
            super.onTouchEvent(event);
            mLongPressHelper.onTouchEvent(event);
            // Keep receiving the rest of the events
            return true;
        } else {
            return super.onTouchEvent(event);
        }
        return result;
    }

    void setStayPressed(boolean stayPressed) {
@@ -531,7 +498,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
    @Override
    public void cancelLongPress() {
        super.cancelLongPress();

        mLongPressHelper.cancelLongPress();
    }

+90 −29
Original line number Diff line number Diff line
@@ -16,46 +16,68 @@

package com.android.launcher3;

import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import com.android.launcher3.util.Thunk;

/**
 * Utility class to handle tripper long press on a view with custom timeout and stylus event
 */
public class CheckLongPressHelper {

    public static final float DEFAULT_LONG_PRESS_TIMEOUT_FACTOR = 0.75f;

    @Thunk View mView;
    @Thunk View.OnLongClickListener mListener;
    @Thunk boolean mHasPerformedLongPress;
    private final View mView;
    private final View.OnLongClickListener mListener;
    private final float mSlop;

    private float mLongPressTimeoutFactor = DEFAULT_LONG_PRESS_TIMEOUT_FACTOR;
    private CheckForLongPress mPendingCheckForLongPress;

    class CheckForLongPress implements Runnable {
        public void run() {
            if ((mView.getParent() != null) && mView.hasWindowFocus()
                    && !mHasPerformedLongPress) {
                boolean handled;
                if (mListener != null) {
                    handled = mListener.onLongClick(mView);
                } else {
                    handled = mView.performLongClick();
                }
                if (handled) {
                    mView.setPressed(false);
                    mHasPerformedLongPress = true;
                }
            }
        }
    }
    private boolean mHasPerformedLongPress;

    private Runnable mPendingCheckForLongPress;

    public CheckLongPressHelper(View v) {
        mView = 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;
        }
    }

    /**
@@ -65,25 +87,64 @@ public class CheckLongPressHelper {
        mLongPressTimeoutFactor = longPressTimeoutFactor;
    }

    public void postCheckForLongPress() {
    private void postCheckForLongPress() {
        mHasPerformedLongPress = false;

        if (mPendingCheckForLongPress == null) {
            mPendingCheckForLongPress = new CheckForLongPress();
            mPendingCheckForLongPress = this::triggerLongPress;
        }
        mView.postDelayed(mPendingCheckForLongPress,
                (long) (ViewConfiguration.getLongPressTimeout() * mLongPressTimeoutFactor));
    }

    /**
     * Cancels any pending long press
     */
    public void cancelLongPress() {
        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() && !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);
    }
}
+0 −25
Original line number Diff line number Diff line
package com.android.launcher3;

import android.view.MotionEvent;
import android.view.View;

import com.android.launcher3.StylusEventHelper.StylusButtonListener;

/**
 * Simple listener that performs a long click on the view after a stylus button press.
 */
public class SimpleOnStylusPressListener implements StylusButtonListener {
    private View mView;

    public SimpleOnStylusPressListener(View view) {
        mView = view;
    }

    public boolean onPressed(MotionEvent event) {
        return mView.isLongClickable() && mView.performLongClick();
    }

    public boolean onReleased(MotionEvent event) {
        return false;
    }
}
 No newline at end of file
+0 −109
Original line number Diff line number Diff line
package com.android.launcher3;

import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

/**
 * Helper for identifying when a stylus touches a view while the primary stylus button is pressed.
 * This can occur in {@value MotionEvent#ACTION_DOWN} or {@value MotionEvent#ACTION_MOVE}.
 */
public class StylusEventHelper {

    /**
     * Implement this interface to receive callbacks for a stylus button press and release.
     */
    public interface StylusButtonListener {
        /**
         * Called when the stylus button is pressed.
         *
         * @param event The MotionEvent that the button press occurred for.
         * @return Whether the event was handled.
         */
        public boolean onPressed(MotionEvent event);

        /**
         * Called when the stylus button is released after a button press. This is also called if
         * the event is canceled or the stylus is lifted off the screen.
         *
         * @param event The MotionEvent the button release occurred for.
         * @return Whether the event was handled.
         */
        public boolean onReleased(MotionEvent event);
    }

    private boolean mIsButtonPressed;
    private View mView;
    private StylusButtonListener mListener;
    private final float mSlop;

    /**
     * Constructs a helper for listening to stylus button presses and releases. Ensure that {
     * {@link #onMotionEvent(MotionEvent)} and {@link #onGenericMotionEvent(MotionEvent)} are called on
     * the helper to correctly identify stylus events.
     *
     * @param listener The listener to call for stylus events.
     * @param view Optional view associated with the touch events.
     */
    public StylusEventHelper(StylusButtonListener listener, View view) {
        mListener = listener;
        mView = view;
        if (mView != null) {
            mSlop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
        } else {
            mSlop = ViewConfiguration.getTouchSlop();
        }
    }

    public boolean onMotionEvent(MotionEvent event) {
        final boolean stylusButtonPressed = isStylusButtonPressed(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mIsButtonPressed = stylusButtonPressed;
                if (mIsButtonPressed) {
                    return mListener.onPressed(event);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (!Utilities.pointInView(mView, event.getX(), event.getY(), mSlop)) {
                    return false;
                }
                if (!mIsButtonPressed && stylusButtonPressed) {
                    mIsButtonPressed = true;
                    return mListener.onPressed(event);
                } else if (mIsButtonPressed && !stylusButtonPressed) {
                    mIsButtonPressed = false;
                    return mListener.onReleased(event);
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (mIsButtonPressed) {
                    mIsButtonPressed = false;
                    return mListener.onReleased(event);
                }
                break;
        }
        return false;
    }

    /**
     * Whether a stylus button press is occurring.
     */
    public boolean inStylusButtonPressed() {
        return mIsButtonPressed;
    }

    /**
     * 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.getButtonState() & MotionEvent.BUTTON_SECONDARY)
                        == MotionEvent.BUTTON_SECONDARY);
    }
}
 No newline at end of file
+4 −31
Original line number Diff line number Diff line
@@ -31,7 +31,6 @@ import android.util.Property;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -52,8 +51,6 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.R;
import com.android.launcher3.SimpleOnStylusPressListener;
import com.android.launcher3.StylusEventHelper;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.WorkspaceItemInfo;
@@ -87,7 +84,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
    private FolderInfo mInfo;

    private CheckLongPressHelper mLongPressHelper;
    private StylusEventHelper mStylusEventHelper;

    static final int DROP_IN_ANIMATION_DURATION = 400;

@@ -110,8 +106,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel

    boolean mAnimating = false;

    private float mSlop;

    private Alarm mOpenAlarm = new Alarm();

    private boolean mForceHideDot;
@@ -149,9 +143,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel

    private void init() {
        mLongPressHelper = new CheckLongPressHelper(this);
        mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
        mPreviewLayoutRule = new ClippedFolderIconLayoutRule();
        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        mPreviewItemManager = new PreviewItemManager(this);
        mDotParams = new DotRenderer.DrawParams();
    }
@@ -663,31 +655,12 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
    public boolean onTouchEvent(MotionEvent event) {
        // Call the superclass onTouchEvent first, because sometimes it changes the state to
        // isPressed() on an ACTION_UP
        boolean result = super.onTouchEvent(event);

        // Check for a stylus button press, if it occurs cancel any long press checks.
        if (mStylusEventHelper.onMotionEvent(event)) {
            mLongPressHelper.cancelLongPress();
        super.onTouchEvent(event);
        mLongPressHelper.onTouchEvent(event);
        // Keep receiving the rest of the events
        return true;
    }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLongPressHelper.postCheckForLongPress();
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                mLongPressHelper.cancelLongPress();
                break;
            case MotionEvent.ACTION_MOVE:
                if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
                    mLongPressHelper.cancelLongPress();
                }
                break;
        }
        return result;
    }

    @Override
    public void cancelLongPress() {
        super.cancelLongPress();
Loading