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.testing;
18 
19 import android.graphics.Point;
20 import android.view.KeyEvent;
21 import android.view.MotionEvent;
22 import android.view.MotionEvent.PointerCoords;
23 import android.view.MotionEvent.PointerProperties;
24 
25 import androidx.annotation.IntDef;
26 
27 import java.lang.annotation.Retention;
28 import java.lang.annotation.RetentionPolicy;
29 import java.util.HashSet;
30 import java.util.Set;
31 
32 /**
33  * Handy-dandy wrapper class to facilitate the creation of MotionEvents.
34  */
35 public final class TestEvents {
36 
37     /**
38      * Common mouse event types...for your convenience.
39      */
40     public static final class Mouse {
41         public static final MotionEvent CLICK =
42                 TestEvents.builder().mouse().primary().build();
43         public static final MotionEvent CTRL_CLICK =
44                 TestEvents.builder().mouse().primary().ctrl().build();
45         public static final MotionEvent ALT_CLICK =
46                 TestEvents.builder().mouse().primary().alt().build();
47         public static final MotionEvent SHIFT_CLICK =
48                 TestEvents.builder().mouse().primary().shift().build();
49         public static final MotionEvent SECONDARY_CLICK =
50                 TestEvents.builder().mouse().secondary().build();
51         public static final MotionEvent TERTIARY_CLICK =
52                 TestEvents.builder().mouse().tertiary().build();
53     }
54 
55     /**
56      * Common touch event types...for your convenience.
57      */
58     public static final class Touch {
59         public static final MotionEvent TAP =
60                 TestEvents.builder().touch().build();
61     }
62 
63     static final int ACTION_UNSET = -1;
64 
65     // Add other actions from MotionEvent.ACTION_ as needed.
66     @IntDef(flag = true, value = {
67             MotionEvent.ACTION_DOWN,
68             MotionEvent.ACTION_MOVE,
69             MotionEvent.ACTION_UP
70     })
71     @Retention(RetentionPolicy.SOURCE)
72     public @interface Action {}
73 
74     // Add other types from MotionEvent.TOOL_TYPE_ as needed.
75     @IntDef(flag = true, value = {
76             MotionEvent.TOOL_TYPE_FINGER,
77             MotionEvent.TOOL_TYPE_MOUSE,
78             MotionEvent.TOOL_TYPE_STYLUS,
79             MotionEvent.TOOL_TYPE_UNKNOWN
80     })
81     @Retention(RetentionPolicy.SOURCE)
82     public @interface ToolType {}
83 
84     @IntDef(flag = true, value = {
85             MotionEvent.BUTTON_PRIMARY,
86             MotionEvent.BUTTON_SECONDARY
87     })
88     @Retention(RetentionPolicy.SOURCE)
89     public @interface Button {}
90 
91     @IntDef(flag = true, value = {
92             KeyEvent.META_SHIFT_ON,
93             KeyEvent.META_CTRL_ON
94     })
95     @Retention(RetentionPolicy.SOURCE)
96     public @interface Key {}
97 
98     private static final class State {
99         private @Action int mAction = ACTION_UNSET;
100         private @ToolType int mToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
101         private int mPointerCount = 1;
102         private Set<Integer> mButtons = new HashSet<>();
103         private Set<Integer> mKeys = new HashSet<>();
104         private Point mLocation = new Point(0, 0);
105         private Point mRawLocation = new Point(0, 0);
106     }
107 
builder()108     public static Builder builder() {
109         return new Builder();
110     }
111 
112     /**
113      * Test event builder with convenience methods for common event attrs.
114      */
115     public static final class Builder {
116 
117         private State mState = new State();
118 
119         /**
120          * @param action Any action specified in {@link MotionEvent}.
121          * @return
122          */
action(int action)123         public Builder action(int action) {
124             mState.mAction = action;
125             return this;
126         }
127 
type(@oolType int type)128         public Builder type(@ToolType int type) {
129             mState.mToolType = type;
130             return this;
131         }
132 
location(int x, int y)133         public Builder location(int x, int y) {
134             mState.mLocation = new Point(x, y);
135             return this;
136         }
137 
rawLocation(int x, int y)138         public Builder rawLocation(int x, int y) {
139             mState.mRawLocation = new Point(x, y);
140             return this;
141         }
142 
pointerCount(int count)143         public Builder pointerCount(int count) {
144             mState.mPointerCount = count;
145             return this;
146         }
147 
148         /**
149          * Adds one or more button press attributes.
150          */
pressButton(@utton int... buttons)151         public Builder pressButton(@Button int... buttons) {
152             for (int button : buttons) {
153                 mState.mButtons.add(button);
154             }
155             return this;
156         }
157 
158         /**
159          * Removes one or more button press attributes.
160          */
releaseButton(@utton int... buttons)161         public Builder releaseButton(@Button int... buttons) {
162             for (int button : buttons) {
163                 mState.mButtons.remove(button);
164             }
165             return this;
166         }
167 
168         /**
169          * Adds one or more key press attributes.
170          */
pressKey(@ey int... keys)171         public Builder pressKey(@Key int... keys) {
172             for (int key : keys) {
173                 mState.mKeys.add(key);
174             }
175             return this;
176         }
177 
178         /**
179          * Removes one or more key press attributes.
180          */
releaseKey(@utton int... keys)181         public Builder releaseKey(@Button int... keys) {
182             for (int key : keys) {
183                 mState.mKeys.remove(key);
184             }
185             return this;
186         }
187 
touch()188         public Builder touch() {
189             type(MotionEvent.TOOL_TYPE_FINGER);
190             return this;
191         }
192 
mouse()193         public Builder mouse() {
194             type(MotionEvent.TOOL_TYPE_MOUSE);
195             return this;
196         }
197 
shift()198         public Builder shift() {
199             pressKey(KeyEvent.META_SHIFT_ON);
200             return this;
201         }
202 
unshift()203         public Builder unshift() {
204             releaseKey(KeyEvent.META_SHIFT_ON);
205             return this;
206         }
207 
ctrl()208         public Builder ctrl() {
209             pressKey(KeyEvent.META_CTRL_ON);
210             return this;
211         }
212 
alt()213         public Builder alt() {
214             pressKey(KeyEvent.META_ALT_ON);
215             return this;
216         }
217 
primary()218         public Builder primary() {
219             pressButton(MotionEvent.BUTTON_PRIMARY);
220             releaseButton(MotionEvent.BUTTON_SECONDARY);
221             releaseButton(MotionEvent.BUTTON_TERTIARY);
222             return this;
223         }
224 
secondary()225         public Builder secondary() {
226             pressButton(MotionEvent.BUTTON_SECONDARY);
227             releaseButton(MotionEvent.BUTTON_PRIMARY);
228             releaseButton(MotionEvent.BUTTON_TERTIARY);
229             return this;
230         }
231 
tertiary()232         public Builder tertiary() {
233             pressButton(MotionEvent.BUTTON_TERTIARY);
234             releaseButton(MotionEvent.BUTTON_PRIMARY);
235             releaseButton(MotionEvent.BUTTON_SECONDARY);
236             return this;
237         }
238 
build()239         public MotionEvent build() {
240 
241             PointerProperties[] pointers = new PointerProperties[1];
242             pointers[0] = new PointerProperties();
243             pointers[0].id = 0;
244             pointers[0].toolType = mState.mToolType;
245 
246             PointerCoords[] coords = new PointerCoords[1];
247             coords[0] = new PointerCoords();
248             coords[0].x = mState.mLocation.x;
249             coords[0].y = mState.mLocation.y;
250 
251             int buttons = 0;
252             for (Integer button : mState.mButtons) {
253                 buttons |= button;
254             }
255 
256             int keys = 0;
257             for (Integer key : mState.mKeys) {
258                 keys |= key;
259             }
260 
261             return MotionEvent.obtain(
262                     0,     // down time
263                     1,     // event time
264                     mState.mAction,
265                     1,  // pointerCount,
266                     pointers,
267                     coords,
268                     keys,
269                     buttons,
270                     1.0f,  // x precision
271                     1.0f,  // y precision
272                     0,     // device id
273                     0,     // edge flags
274                     0,     // int source,
275                     0      // int flags
276                     );
277         }
278     }
279 }
280