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
17package androidx.recyclerview.selection.testing;
18
19import android.graphics.Point;
20import android.view.KeyEvent;
21import android.view.MotionEvent;
22import android.view.MotionEvent.PointerCoords;
23import android.view.MotionEvent.PointerProperties;
24
25import androidx.annotation.IntDef;
26
27import java.lang.annotation.Retention;
28import java.lang.annotation.RetentionPolicy;
29import java.util.HashSet;
30import java.util.Set;
31
32/**
33 * Handy-dandy wrapper class to facilitate the creation of MotionEvents.
34 */
35public 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
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         */
123        public Builder action(int action) {
124            mState.mAction = action;
125            return this;
126        }
127
128        public Builder type(@ToolType int type) {
129            mState.mToolType = type;
130            return this;
131        }
132
133        public Builder location(int x, int y) {
134            mState.mLocation = new Point(x, y);
135            return this;
136        }
137
138        public Builder rawLocation(int x, int y) {
139            mState.mRawLocation = new Point(x, y);
140            return this;
141        }
142
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         */
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         */
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         */
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         */
181        public Builder releaseKey(@Button int... keys) {
182            for (int key : keys) {
183                mState.mKeys.remove(key);
184            }
185            return this;
186        }
187
188        public Builder touch() {
189            type(MotionEvent.TOOL_TYPE_FINGER);
190            return this;
191        }
192
193        public Builder mouse() {
194            type(MotionEvent.TOOL_TYPE_MOUSE);
195            return this;
196        }
197
198        public Builder shift() {
199            pressKey(KeyEvent.META_SHIFT_ON);
200            return this;
201        }
202
203        public Builder unshift() {
204            releaseKey(KeyEvent.META_SHIFT_ON);
205            return this;
206        }
207
208        public Builder ctrl() {
209            pressKey(KeyEvent.META_CTRL_ON);
210            return this;
211        }
212
213        public Builder alt() {
214            pressKey(KeyEvent.META_ALT_ON);
215            return this;
216        }
217
218        public Builder primary() {
219            pressButton(MotionEvent.BUTTON_PRIMARY);
220            releaseButton(MotionEvent.BUTTON_SECONDARY);
221            releaseButton(MotionEvent.BUTTON_TERTIARY);
222            return this;
223        }
224
225        public Builder secondary() {
226            pressButton(MotionEvent.BUTTON_SECONDARY);
227            releaseButton(MotionEvent.BUTTON_PRIMARY);
228            releaseButton(MotionEvent.BUTTON_TERTIARY);
229            return this;
230        }
231
232        public Builder tertiary() {
233            pressButton(MotionEvent.BUTTON_TERTIARY);
234            releaseButton(MotionEvent.BUTTON_PRIMARY);
235            releaseButton(MotionEvent.BUTTON_SECONDARY);
236            return this;
237        }
238
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