1package com.android.launcher3;
2
3import android.view.MotionEvent;
4import android.view.View;
5import android.view.ViewConfiguration;
6
7/**
8 * Helper for identifying when a stylus touches a view while the primary stylus button is pressed.
9 * This can occur in {@value MotionEvent#ACTION_DOWN} or {@value MotionEvent#ACTION_MOVE}.
10 */
11public class StylusEventHelper {
12
13    /**
14     * Implement this interface to receive callbacks for a stylus button press and release.
15     */
16    public interface StylusButtonListener {
17        /**
18         * Called when the stylus button is pressed.
19         *
20         * @param event The MotionEvent that the button press occurred for.
21         * @return Whether the event was handled.
22         */
23        public boolean onPressed(MotionEvent event);
24
25        /**
26         * Called when the stylus button is released after a button press. This is also called if
27         * the event is canceled or the stylus is lifted off the screen.
28         *
29         * @param event The MotionEvent the button release occurred for.
30         * @return Whether the event was handled.
31         */
32        public boolean onReleased(MotionEvent event);
33    }
34
35    private boolean mIsButtonPressed;
36    private View mView;
37    private StylusButtonListener mListener;
38    private final float mSlop;
39
40    /**
41     * Constructs a helper for listening to stylus button presses and releases. Ensure that {
42     * {@link #onMotionEvent(MotionEvent)} and {@link #onGenericMotionEvent(MotionEvent)} are called on
43     * the helper to correctly identify stylus events.
44     *
45     * @param listener The listener to call for stylus events.
46     * @param view Optional view associated with the touch events.
47     */
48    public StylusEventHelper(StylusButtonListener listener, View view) {
49        mListener = listener;
50        mView = view;
51        if (mView != null) {
52            mSlop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
53        } else {
54            mSlop = ViewConfiguration.getTouchSlop();
55        }
56    }
57
58    public boolean onMotionEvent(MotionEvent event) {
59        final boolean stylusButtonPressed = isStylusButtonPressed(event);
60        switch (event.getAction()) {
61            case MotionEvent.ACTION_DOWN:
62                mIsButtonPressed = stylusButtonPressed;
63                if (mIsButtonPressed) {
64                    return mListener.onPressed(event);
65                }
66                break;
67            case MotionEvent.ACTION_MOVE:
68                if (!Utilities.pointInView(mView, event.getX(), event.getY(), mSlop)) {
69                    return false;
70                }
71                if (!mIsButtonPressed && stylusButtonPressed) {
72                    mIsButtonPressed = true;
73                    return mListener.onPressed(event);
74                } else if (mIsButtonPressed && !stylusButtonPressed) {
75                    mIsButtonPressed = false;
76                    return mListener.onReleased(event);
77                }
78                break;
79            case MotionEvent.ACTION_UP:
80            case MotionEvent.ACTION_CANCEL:
81                if (mIsButtonPressed) {
82                    mIsButtonPressed = false;
83                    return mListener.onReleased(event);
84                }
85                break;
86        }
87        return false;
88    }
89
90    /**
91     * Whether a stylus button press is occurring.
92     */
93    public boolean inStylusButtonPressed() {
94        return mIsButtonPressed;
95    }
96
97    /**
98     * Identifies if the provided {@link MotionEvent} is a stylus with the primary stylus button
99     * pressed.
100     *
101     * @param event The event to check.
102     * @return Whether a stylus button press occurred.
103     */
104    private static boolean isStylusButtonPressed(MotionEvent event) {
105        return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
106                && ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY)
107                        == MotionEvent.BUTTON_SECONDARY);
108    }
109}