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