1 /*
2  * Copyright 2017 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 androidx.recyclerview.selection;
18 
19 import android.graphics.Point;
20 import android.view.InputDevice;
21 import android.view.KeyEvent;
22 import android.view.MotionEvent;
23 
24 import org.jspecify.annotations.NonNull;
25 
26 /**
27  * Utility methods for working with {@link MotionEvent} instances.
28  */
29 final class MotionEvents {
30 
MotionEvents()31     private MotionEvents() {
32     }
33 
isTouchpadEvent(@onNull MotionEvent e)34     static boolean isTouchpadEvent(@NonNull MotionEvent e) {
35         // ChromeOS ARC devices with touchpads emit their events with
36         // {@link MotionEvent#TOOL_TYPE_MOUSE}, so this is specifically capturing non-ARC devices
37         // with touchpads (e.g. attachable keyboards with touchpads on Android tablets).
38         return e.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER
39                 && e.getSource() == InputDevice.SOURCE_MOUSE;
40     }
41 
isMouseEvent(@onNull MotionEvent e)42     static boolean isMouseEvent(@NonNull MotionEvent e) {
43         return e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE;
44     }
45 
isFingerEvent(@onNull MotionEvent e)46     static boolean isFingerEvent(@NonNull MotionEvent e) {
47         return e.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER;
48     }
49 
isActionDown(@onNull MotionEvent e)50     static boolean isActionDown(@NonNull MotionEvent e) {
51         return e.getActionMasked() == MotionEvent.ACTION_DOWN;
52     }
53 
isActionMove(@onNull MotionEvent e)54     static boolean isActionMove(@NonNull MotionEvent e) {
55         return e.getActionMasked() == MotionEvent.ACTION_MOVE;
56     }
57 
isActionUp(@onNull MotionEvent e)58     static boolean isActionUp(@NonNull MotionEvent e) {
59         return e.getActionMasked() == MotionEvent.ACTION_UP;
60     }
61 
isActionPointerUp(@onNull MotionEvent e)62     static boolean isActionPointerUp(@NonNull MotionEvent e) {
63         return e.getActionMasked() == MotionEvent.ACTION_POINTER_UP;
64     }
65 
66     @SuppressWarnings("unused")
isActionPointerDown(@onNull MotionEvent e)67     static boolean isActionPointerDown(@NonNull MotionEvent e) {
68         return e.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN;
69     }
70 
isActionCancel(@onNull MotionEvent e)71     static boolean isActionCancel(@NonNull MotionEvent e) {
72         return e.getActionMasked() == MotionEvent.ACTION_CANCEL;
73     }
74 
getOrigin(@onNull MotionEvent e)75     static Point getOrigin(@NonNull MotionEvent e) {
76         return new Point((int) e.getX(), (int) e.getY());
77     }
78 
isPrimaryMouseButtonPressed(@onNull MotionEvent e)79     static boolean isPrimaryMouseButtonPressed(@NonNull MotionEvent e) {
80         return isButtonPressed(e, MotionEvent.BUTTON_PRIMARY);
81     }
82 
isSecondaryMouseButtonPressed(@onNull MotionEvent e)83     static boolean isSecondaryMouseButtonPressed(@NonNull MotionEvent e) {
84         return isButtonPressed(e, MotionEvent.BUTTON_SECONDARY);
85     }
86 
isTertiaryMouseButtonPressed(@onNull MotionEvent e)87     static boolean isTertiaryMouseButtonPressed(@NonNull MotionEvent e) {
88         return isButtonPressed(e, MotionEvent.BUTTON_TERTIARY);
89     }
90 
91     // NOTE: Can replace this with MotionEvent.isButtonPressed once targeting 21 or higher.
isButtonPressed(MotionEvent e, int button)92     private static boolean isButtonPressed(MotionEvent e, int button) {
93         if (button == 0) {
94             return false;
95         }
96         return (e.getButtonState() & button) == button;
97     }
98 
isShiftKeyPressed(@onNull MotionEvent e)99     static boolean isShiftKeyPressed(@NonNull MotionEvent e) {
100         return hasBit(e.getMetaState(), KeyEvent.META_SHIFT_ON);
101     }
102 
isCtrlKeyPressed(@onNull MotionEvent e)103     static boolean isCtrlKeyPressed(@NonNull MotionEvent e) {
104         return hasBit(e.getMetaState(), KeyEvent.META_CTRL_ON);
105     }
106 
isAltKeyPressed(@onNull MotionEvent e)107     static boolean isAltKeyPressed(@NonNull MotionEvent e) {
108         return hasBit(e.getMetaState(), KeyEvent.META_ALT_ON);
109     }
110 
isTouchpadScroll(@onNull MotionEvent e)111     static boolean isTouchpadScroll(@NonNull MotionEvent e) {
112         // Touchpad inputs are treated as mouse inputs, and when scrolling, there are no buttons
113         // returned.
114         return (isTouchpadEvent(e) || isMouseEvent(e)) && isActionMove(e)
115                 && e.getButtonState() == 0;
116     }
117 
118     /**
119      * Returns true if the event is a drag event (which is presumbaly, but not
120      * explicitly required to be a mouse event).
121      */
isPointerDragEvent(MotionEvent e)122     static boolean isPointerDragEvent(MotionEvent e) {
123         return isPrimaryMouseButtonPressed(e)
124                 && isActionMove(e);
125     }
126 
hasBit(int metaState, int bit)127     private static boolean hasBit(int metaState, int bit) {
128         return (metaState & bit) != 0;
129     }
130 
createCancelEvent()131     static MotionEvent createCancelEvent() {
132         return MotionEvent.obtain(
133                 0,     // down time
134                 1,     // event time
135                 MotionEvent.ACTION_CANCEL,
136                 0,  // x
137                 0,  // y
138                 0  // metaState
139         );
140     }
141 }
142