1 package com.android.launcher3;
2 
3 import android.view.MotionEvent;
4 import android.view.View;
5 import 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  */
11 public 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          */
onPressed(MotionEvent event)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          */
onReleased(MotionEvent event)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      */
StylusEventHelper(StylusButtonListener listener, View view)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 
onMotionEvent(MotionEvent event)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      */
inStylusButtonPressed()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      */
isStylusButtonPressed(MotionEvent event)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 }