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.InputDevice;
21 import android.view.KeyEvent;
22 import android.view.MotionEvent;
23 import android.view.MotionEvent.PointerCoords;
24 import android.view.MotionEvent.PointerProperties;
25 
26 import androidx.annotation.IntDef;
27 
28 import java.lang.annotation.Retention;
29 import java.lang.annotation.RetentionPolicy;
30 import java.util.HashSet;
31 import java.util.Set;
32 
33 /**
34  * Handy-dandy wrapper class to facilitate the creation of MotionEvents.
35  */
36 public final class TestEvents {
37 
38     /**
39      * Common mouse event types...for your convenience.
40      */
41     public static final class Mouse {
42         public static final MotionEvent CLICK = TestEvents.builder()
43                 .mouse()
44                 .primary()
45                 .down()
46                 .build();
47 
48         public static final MotionEvent CTRL_CLICK = TestEvents.builder()
49                 .mouse()
50                 .primary()
51                 .down()
52                 .ctrl()
53                 .build();
54 
55         public static final MotionEvent ALT_CLICK = TestEvents.builder()
56                 .mouse()
57                 .primary()
58                 .down()
59                 .alt()
60                 .build();
61 
62         public static final MotionEvent SHIFT_CLICK = TestEvents.builder()
63                 .mouse()
64                 .primary()
65                 .down()
66                 .shift()
67                 .build();
68 
69         public static final MotionEvent SECONDARY_CLICK = TestEvents.builder()
70                 .mouse()
71                 .secondary()
72                 .down()
73                 .build();
74 
75         public static final MotionEvent TERTIARY_CLICK = TestEvents.builder()
76                 .mouse()
77                 .tertiary()
78                 .down()
79                 .build();
80 
81         public static final MotionEvent MOVE = TestEvents.builder()
82                 .mouse()
83                 .move()
84                 .build();
85 
86         public static final MotionEvent PRIMARY_DRAG = TestEvents.builder()
87                 .mouse()
88                 .primary()
89                 .move()
90                 .build();
91 
92         public static final MotionEvent SECONDARY_DRAG = TestEvents.builder()
93                 .mouse()
94                 .secondary()
95                 .move()
96                 .build();
97 
98         public static final MotionEvent TERTIARY_DRAG = TestEvents.builder()
99                 .mouse()
100                 .tertiary()
101                 .move()
102                 .build();
103 
104         public static final MotionEvent UP = TestEvents.builder()
105                 .mouse()
106                 .up()
107                 .build();
108 
109         public static final MotionEvent DOWN = TestEvents.builder()
110                 .mouse()
111                 .move()
112                 .build();
113 
114         // NOTE: POINTER_DOWN and POINTER_UP are for secondary pointers, not main mouse pointer.
115         public static final MotionEvent POINTER_DOWN =
116                 TestEvents.builder()
117                         .mouse()
118                         .down()
119                         .build();
120 
121         public static final MotionEvent POINTER_UP =
122                 TestEvents.builder()
123                         .mouse()
124                         .action(MotionEvent.ACTION_POINTER_UP)
125                         .build();
126     }
127 
128     /**
129      * Common touch event types...for your convenience.
130      */
131     public static final class Touch {
132 
133         public static final MotionEvent DOWN = TestEvents.builder()
134                 .down()
135                 .touch()
136                 .location(1, 1)
137                 .build();
138 
139         public static final MotionEvent MOVE = TestEvents.builder()
140                 .move()
141                 .touch()
142                 .location(1, 1)
143                 .build();
144 
145         public static final MotionEvent UP = TestEvents.builder()
146                 .up()
147                 .touch()
148                 .location(1, 1)
149                 .build();
150 
151         public static final MotionEvent TAP = TestEvents.builder()
152                 .touch()
153                 .build();
154     }
155 
156     /**
157      * Common touch event types...for your convenience.
158      */
159     public static final class Unknown {
160         public static final MotionEvent CANCEL = TestEvents.builder()
161                 .action(MotionEvent.ACTION_CANCEL)
162                 .build();
163     }
164 
165     static final int ACTION_UNSET = -1;
166 
167     // Add other actions from MotionEvent.ACTION_ as needed.
168     @IntDef(flag = true, value = {
169             MotionEvent.ACTION_DOWN,
170             MotionEvent.ACTION_MOVE,
171             MotionEvent.ACTION_UP,
172             MotionEvent.ACTION_CANCEL,
173     })
174     @Retention(RetentionPolicy.SOURCE)
175     public @interface Action {
176     }
177 
178     // Add other types from MotionEvent.TOOL_TYPE_ as needed.
179     @IntDef(flag = true, value = {
180             MotionEvent.TOOL_TYPE_FINGER,
181             MotionEvent.TOOL_TYPE_MOUSE,
182             MotionEvent.TOOL_TYPE_STYLUS,
183             MotionEvent.TOOL_TYPE_UNKNOWN,
184     })
185     @Retention(RetentionPolicy.SOURCE)
186     public @interface ToolType {
187     }
188 
189     // Add other types from InputDevice.SOURCE_* as needed.
190     @IntDef(flag = true, value = {
191             InputDevice.SOURCE_MOUSE,
192             InputDevice.SOURCE_UNKNOWN,
193     })
194     @Retention(RetentionPolicy.SOURCE)
195     public @interface Source {
196     }
197 
198     @IntDef(flag = true, value = {
199             MotionEvent.BUTTON_PRIMARY,
200             MotionEvent.BUTTON_SECONDARY,
201     })
202     @Retention(RetentionPolicy.SOURCE)
203     public @interface Button {
204     }
205 
206     @IntDef(flag = true, value = {
207             KeyEvent.META_SHIFT_ON,
208             KeyEvent.META_CTRL_ON,
209     })
210     @Retention(RetentionPolicy.SOURCE)
211     public @interface Key {
212     }
213 
214     private static final class State {
215         private @Action int mAction = ACTION_UNSET;
216         private @ToolType int mToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
217         private @Source int mSource = InputDevice.SOURCE_UNKNOWN;
218         private int mPointerCount = 1;
219         private Set<Integer> mButtons = new HashSet<>();
220         private Set<Integer> mKeys = new HashSet<>();
221         private Point mLocation = new Point(0, 0);
222         private Point mRawLocation = new Point(0, 0);
223     }
224 
builder()225     public static Builder builder() {
226         return new Builder();
227     }
228 
229     /**
230      * Test event builder with convenience methods for common event attrs.
231      */
232     public static final class Builder {
233 
234         private State mState = new State();
235 
236         /**
237          * @param action Any action specified in {@link MotionEvent}.
238          */
action(int action)239         public Builder action(int action) {
240             mState.mAction = action;
241             return this;
242         }
243 
type(@oolType int type)244         public Builder type(@ToolType int type) {
245             mState.mToolType = type;
246             return this;
247         }
248 
249         /**
250          * @param source Any source type specified in {@link InputDevice}. When adding a new
251          *               source, ensure it is also added to the IntDef for @Source.
252          */
source(@ource int source)253         public Builder source(@Source int source) {
254             mState.mSource = source;
255             return this;
256         }
257 
location(int x, int y)258         public Builder location(int x, int y) {
259             mState.mLocation = new Point(x, y);
260             return this;
261         }
262 
rawLocation(int x, int y)263         public Builder rawLocation(int x, int y) {
264             mState.mRawLocation = new Point(x, y);
265             return this;
266         }
267 
pointerCount(int count)268         public Builder pointerCount(int count) {
269             mState.mPointerCount = count;
270             return this;
271         }
272 
273         /**
274          * Adds one or more button press attributes.
275          */
pressButton(@utton int... buttons)276         public Builder pressButton(@Button int... buttons) {
277             for (int button : buttons) {
278                 mState.mButtons.add(button);
279             }
280             return this;
281         }
282 
283         /**
284          * Removes one or more button press attributes.
285          */
releaseButton(@utton int... buttons)286         public Builder releaseButton(@Button int... buttons) {
287             for (int button : buttons) {
288                 mState.mButtons.remove(button);
289             }
290             return this;
291         }
292 
293         /**
294          * Adds one or more key press attributes.
295          */
pressKey(@ey int... keys)296         public Builder pressKey(@Key int... keys) {
297             for (int key : keys) {
298                 mState.mKeys.add(key);
299             }
300             return this;
301         }
302 
303         /**
304          * Removes one or more key press attributes.
305          */
releaseKey(@utton int... keys)306         public Builder releaseKey(@Button int... keys) {
307             for (int key : keys) {
308                 mState.mKeys.remove(key);
309             }
310             return this;
311         }
312 
up()313         public Builder up() {
314             action(MotionEvent.ACTION_UP);
315             return this;
316         }
317 
318         /**
319          * Sets action to MotionEvent#ACTION_DOWN which can be used
320          * with most tool types including mouse.
321          *
322          * <p>NOTE: ACTION_POINTER_DOWN is used for secondary pointers.
323          */
down()324         public Builder down() {
325             action(MotionEvent.ACTION_DOWN);
326             return this;
327         }
328 
move()329         public Builder move() {
330             action(MotionEvent.ACTION_MOVE);
331             return this;
332         }
333 
touch()334         public Builder touch() {
335             type(MotionEvent.TOOL_TYPE_FINGER);
336             return this;
337         }
338 
mouse()339         public Builder mouse() {
340             type(MotionEvent.TOOL_TYPE_MOUSE);
341             return this;
342         }
343 
unknown()344         public Builder unknown() {
345             type(MotionEvent.TOOL_TYPE_UNKNOWN);
346             return this;
347         }
348 
shift()349         public Builder shift() {
350             pressKey(KeyEvent.META_SHIFT_ON);
351             return this;
352         }
353 
unshift()354         public Builder unshift() {
355             releaseKey(KeyEvent.META_SHIFT_ON);
356             return this;
357         }
358 
ctrl()359         public Builder ctrl() {
360             pressKey(KeyEvent.META_CTRL_ON);
361             return this;
362         }
363 
alt()364         public Builder alt() {
365             pressKey(KeyEvent.META_ALT_ON);
366             return this;
367         }
368 
primary()369         public Builder primary() {
370             pressButton(MotionEvent.BUTTON_PRIMARY);
371             releaseButton(MotionEvent.BUTTON_SECONDARY);
372             releaseButton(MotionEvent.BUTTON_TERTIARY);
373             return this;
374         }
375 
secondary()376         public Builder secondary() {
377             pressButton(MotionEvent.BUTTON_SECONDARY);
378             releaseButton(MotionEvent.BUTTON_PRIMARY);
379             releaseButton(MotionEvent.BUTTON_TERTIARY);
380             return this;
381         }
382 
tertiary()383         public Builder tertiary() {
384             pressButton(MotionEvent.BUTTON_TERTIARY);
385             releaseButton(MotionEvent.BUTTON_PRIMARY);
386             releaseButton(MotionEvent.BUTTON_SECONDARY);
387             return this;
388         }
389 
build()390         public MotionEvent build() {
391 
392             PointerProperties[] pointers = new PointerProperties[1];
393             pointers[0] = new PointerProperties();
394             pointers[0].id = 0;
395             pointers[0].toolType = mState.mToolType;
396 
397             PointerCoords[] coords = new PointerCoords[1];
398             coords[0] = new PointerCoords();
399             coords[0].x = mState.mLocation.x;
400             coords[0].y = mState.mLocation.y;
401 
402             int buttons = 0;
403             for (Integer button : mState.mButtons) {
404                 buttons |= button;
405             }
406 
407             int keys = 0;
408             for (Integer key : mState.mKeys) {
409                 keys |= key;
410             }
411 
412             return MotionEvent.obtain(
413                     0,     // down time
414                     1,     // event time
415                     mState.mAction,
416                     1,  // pointerCount,
417                     pointers,
418                     coords,
419                     keys,
420                     buttons,
421                     1.0f,            // x precision
422                     1.0f,            // y precision
423                     0,               // device id
424                     0,               // edge flags
425                     mState.mSource,  // int source,
426                     0                // int flags
427             );
428         }
429     }
430 }
431