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 android.annotation.IntDef;
20 import android.graphics.Point;
21 import android.support.v7.widget.RecyclerView;
22 import android.text.TextUtils;
23 import android.view.KeyEvent;
24 import android.view.MotionEvent;
25 
26 import com.android.documentsui.base.Events.InputEvent;
27 import com.android.documentsui.dirlist.DocumentDetails;
28 
29 import java.lang.annotation.Retention;
30 import java.lang.annotation.RetentionPolicy;
31 import java.util.HashSet;
32 import java.util.Set;
33 
34 /**
35  * Events and DocDetails are closely related. For the pursposes of this test
36  * we coalesce the two in a single, handy-dandy test class.
37  */
38 public class TestEvent implements InputEvent {
39     private static final int ACTION_UNSET = -1;
40 
41     // Add other actions from MotionEvent.ACTION_ as needed.
42     @IntDef(flag = true, value = {
43             MotionEvent.ACTION_DOWN,
44             MotionEvent.ACTION_MOVE,
45             MotionEvent.ACTION_UP
46     })
47     @Retention(RetentionPolicy.SOURCE)
48     public @interface Action {}
49 
50     // Add other types from MotionEvent.TOOL_TYPE_ as needed.
51     @IntDef(flag = true, value = {
52             MotionEvent.TOOL_TYPE_FINGER,
53             MotionEvent.TOOL_TYPE_MOUSE,
54             MotionEvent.TOOL_TYPE_STYLUS,
55             MotionEvent.TOOL_TYPE_UNKNOWN
56     })
57     @Retention(RetentionPolicy.SOURCE)
58     public @interface ToolType {}
59 
60     @IntDef(flag = true, value = {
61             MotionEvent.BUTTON_PRIMARY,
62             MotionEvent.BUTTON_SECONDARY
63     })
64     @Retention(RetentionPolicy.SOURCE)
65     public @interface Button {}
66 
67     @IntDef(flag = true, value = {
68             KeyEvent.META_SHIFT_ON,
69             KeyEvent.META_CTRL_ON
70     })
71     @Retention(RetentionPolicy.SOURCE)
72     public @interface Key {}
73 
74     private @Action int mAction;
75     private @ToolType int mToolType;
76     private int mPointerCount;
77     private Set<Integer> mButtons;
78     private Set<Integer> mKeys;
79     private Point mLocation;
80     private Point mRawLocation;
81     private Details mDetails;
82 
TestEvent()83     private TestEvent() {
84         mAction = ACTION_UNSET;  // somebody has to set this, else we'll barf later.
85         mToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
86         mButtons = new HashSet<>();
87         mKeys = new HashSet<>();
88         mLocation = new Point(0, 0);
89         mRawLocation = new Point(0, 0);
90         mDetails = new Details();
91         mPointerCount = 0;
92     }
93 
TestEvent(TestEvent source)94     private TestEvent(TestEvent source) {
95         assert(source.mAction != ACTION_UNSET);
96         mAction = source.mAction;
97         mToolType = source.mToolType;
98         mButtons = source.mButtons;
99         mKeys = source.mKeys;
100         mLocation = source.mLocation;
101         mRawLocation = source.mRawLocation;
102         mDetails = new Details(source.mDetails);
103         mPointerCount = source.mPointerCount;
104     }
105 
106     @Override
getOrigin()107     public Point getOrigin() {
108         return mLocation;
109     }
110 
111     @Override
getX()112     public float getX() {
113         return mLocation.x;
114     }
115 
116     @Override
getY()117     public float getY() {
118         return mLocation.y;
119     }
120 
121     @Override
getRawX()122     public float getRawX() {
123         return mRawLocation.x;
124     }
125 
126     @Override
getRawY()127     public float getRawY() {
128         return mRawLocation.y;
129     }
130 
131     @Override
getPointerCount()132     public int getPointerCount() {
133         return mPointerCount;
134     }
135 
136     @Override
isMouseEvent()137     public boolean isMouseEvent() {
138         return mToolType == MotionEvent.TOOL_TYPE_MOUSE;
139     }
140 
141     @Override
isPrimaryButtonPressed()142     public boolean isPrimaryButtonPressed() {
143         return mButtons.contains(MotionEvent.BUTTON_PRIMARY);
144     }
145 
146     @Override
isSecondaryButtonPressed()147     public boolean isSecondaryButtonPressed() {
148         return mButtons.contains(MotionEvent.BUTTON_SECONDARY);
149     }
150 
151     @Override
isTertiaryButtonPressed()152     public boolean isTertiaryButtonPressed() {
153         return mButtons.contains(MotionEvent.BUTTON_TERTIARY);
154     }
155 
156     @Override
isShiftKeyDown()157     public boolean isShiftKeyDown() {
158         return mKeys.contains(KeyEvent.META_SHIFT_ON);
159     }
160 
161     @Override
isCtrlKeyDown()162     public boolean isCtrlKeyDown() {
163         return mKeys.contains(KeyEvent.META_CTRL_ON);
164     }
165 
166     @Override
isAltKeyDown()167     public boolean isAltKeyDown() {
168         return mKeys.contains(KeyEvent.META_ALT_ON);
169     }
170 
171     @Override
isActionDown()172     public boolean isActionDown() {
173         return mAction == MotionEvent.ACTION_DOWN;
174     }
175 
176     @Override
isActionUp()177     public boolean isActionUp() {
178         return mAction == MotionEvent.ACTION_UP;
179     }
180 
181     @Override
isMultiPointerActionDown()182     public boolean isMultiPointerActionDown() {
183         return mAction == MotionEvent.ACTION_POINTER_DOWN;
184     }
185 
186     @Override
isMultiPointerActionUp()187     public boolean isMultiPointerActionUp() {
188         return mAction == MotionEvent.ACTION_POINTER_UP;
189     }
190 
191     @Override
isActionMove()192     public boolean isActionMove() {
193         return mAction == MotionEvent.ACTION_MOVE;
194     }
195 
196     @Override
isActionCancel()197     public boolean isActionCancel() {
198         return mAction == MotionEvent.ACTION_CANCEL;
199     }
200 
201     @Override
isOverItem()202     public boolean isOverItem() {
203         return mDetails.isOverItem();
204     }
205 
206     @Override
isOverDragHotspot()207     public boolean isOverDragHotspot() {
208         return isOverItem() && mDetails.isInDragHotspot(this);
209     }
210 
211     @Override
isOverModelItem()212     public boolean isOverModelItem() {
213         if (isOverItem()) {
214             DocumentDetails doc = getDocumentDetails();
215             return doc != null && doc.hasModelId();
216         }
217         return false;
218     }
219 
220     @Override
isTouchpadScroll()221     public boolean isTouchpadScroll() {
222         return isMouseEvent() && mButtons.isEmpty() && isActionMove();
223     }
224 
225     @Override
getItemPosition()226     public int getItemPosition() {
227         return mDetails.mPosition;
228     }
229 
230     @Override
getDocumentDetails()231     public DocumentDetails getDocumentDetails() {
232         return mDetails;
233     }
234 
235     @Override
close()236     public void close() {}
237 
238     @Override
hashCode()239     public int hashCode() {
240         return mDetails.hashCode();
241     }
242 
243     @Override
equals(Object o)244     public boolean equals(Object o) {
245       if (this == o) {
246           return true;
247       }
248 
249       if (!(o instanceof TestEvent)) {
250           return false;
251       }
252 
253       TestEvent other = (TestEvent) o;
254       return mAction == other.mAction
255               && mToolType == other.mToolType
256               && mButtons.equals(other.mButtons)
257               && mKeys.equals(other.mKeys)
258               && mLocation.equals(other.mLocation)
259               && mRawLocation.equals(other.mRawLocation)
260               && mDetails.equals(other.mDetails);
261     }
262 
263     private static final class Details implements DocumentDetails {
264 
265         private int mPosition;
266         private String mModelId;
267         private boolean mInSelectionHotspot;
268         private boolean mInDragHotspot;
269 
Details()270         public Details() {
271            mPosition = Integer.MIN_VALUE;
272         }
273 
Details(Details source)274         public Details(Details source) {
275             mPosition = source.mPosition;
276             mModelId = source.mModelId;
277             mInSelectionHotspot = source.mInSelectionHotspot;
278             mInDragHotspot = source.mInDragHotspot;
279         }
280 
281 
isOverItem()282         private boolean isOverItem() {
283             return mPosition != Integer.MIN_VALUE && mPosition != RecyclerView.NO_POSITION;
284         }
285 
286         @Override
hasModelId()287         public boolean hasModelId() {
288             return !TextUtils.isEmpty(mModelId);
289         }
290 
291         @Override
getModelId()292         public String getModelId() {
293             return mModelId;
294         }
295 
296         @Override
getAdapterPosition()297         public int getAdapterPosition() {
298             return mPosition;
299         }
300 
301         @Override
isInSelectionHotspot(InputEvent event)302         public boolean isInSelectionHotspot(InputEvent event) {
303             return mInSelectionHotspot;
304         }
305 
306         @Override
isInDragHotspot(InputEvent event)307         public boolean isInDragHotspot(InputEvent event) {
308             return mInDragHotspot;
309         }
310 
311         @Override
hashCode()312         public int hashCode() {
313             return mModelId != null ? mModelId.hashCode() : ACTION_UNSET;
314         }
315 
316         @Override
equals(Object o)317         public boolean equals(Object o) {
318           if (this == o) {
319               return true;
320           }
321 
322           if (!(o instanceof Details)) {
323               return false;
324           }
325 
326           Details other = (Details) o;
327           return mPosition == other.mPosition
328                   && mModelId == other.mModelId;
329         }
330     }
331 
builder()332     public static final Builder builder() {
333         return new Builder();
334     }
335 
336     /**
337      * Test event builder with convenience methods for common event attrs.
338      */
339     public static final class Builder {
340 
341         private TestEvent mState = new TestEvent();
342 
Builder()343         public Builder() {
344         }
345 
Builder(TestEvent state)346         public Builder(TestEvent state) {
347             mState = new TestEvent(state);
348         }
349 
350         /**
351          * @param action Any action specified in {@link MotionEvent}.
352          * @return
353          */
action(int action)354         public Builder action(int action) {
355             mState.mAction = action;
356             return this;
357         }
358 
type(@oolType int type)359         public Builder type(@ToolType int type) {
360             mState.mToolType = type;
361             return this;
362         }
363 
location(int x, int y)364         public Builder location(int x, int y) {
365             mState.mLocation = new Point(x, y);
366             return this;
367         }
368 
rawLocation(int x, int y)369         public Builder rawLocation(int x, int y) {
370             mState.mRawLocation = new Point(x, y);
371             return this;
372         }
373 
pointerCount(int count)374         public Builder pointerCount(int count) {
375             mState.mPointerCount = count;
376             return this;
377         }
378 
379         /**
380          * Adds one or more button press attributes.
381          */
pressButton(@utton int... buttons)382         public Builder pressButton(@Button int... buttons) {
383             for (int button : buttons) {
384                 mState.mButtons.add(button);
385             }
386             return this;
387         }
388 
389         /**
390          * Removes one or more button press attributes.
391          */
releaseButton(@utton int... buttons)392         public Builder releaseButton(@Button int... buttons) {
393             for (int button : buttons) {
394                 mState.mButtons.remove(button);
395             }
396             return this;
397         }
398 
399         /**
400          * Adds one or more key press attributes.
401          */
pressKey(@ey int... keys)402         public Builder pressKey(@Key int... keys) {
403             for (int key : keys) {
404                 mState.mKeys.add(key);
405             }
406             return this;
407         }
408 
409         /**
410          * Removes one or more key press attributes.
411          */
releaseKey(@utton int... keys)412         public Builder releaseKey(@Button int... keys) {
413             for (int key : keys) {
414                 mState.mKeys.remove(key);
415             }
416             return this;
417         }
418 
at(int position)419         public Builder at(int position) {
420             mState.mDetails.mPosition = position;  // this is both "adapter position" and "item position".
421             mState.mDetails.mModelId = String.valueOf(position);
422             return this;
423         }
424 
inSelectionHotspot()425         public Builder inSelectionHotspot() {
426             mState.mDetails.mInSelectionHotspot = true;
427             return this;
428         }
429 
inDragHotspot()430         public Builder inDragHotspot() {
431             mState.mDetails.mInDragHotspot = true;
432             return this;
433         }
434 
notInDragHotspot()435         public Builder notInDragHotspot() {
436             mState.mDetails.mInDragHotspot = false;
437             return this;
438         }
439 
touch()440         public Builder touch() {
441             type(MotionEvent.TOOL_TYPE_FINGER);
442             return this;
443         }
444 
mouse()445         public Builder mouse() {
446             type(MotionEvent.TOOL_TYPE_MOUSE);
447             return this;
448         }
449 
shift()450         public Builder shift() {
451             pressKey(KeyEvent.META_SHIFT_ON);
452             return this;
453         }
454 
455         /**
456          * Use {@link #remove(@Attribute int...)}
457          */
458         @Deprecated
unshift()459         public Builder unshift() {
460             releaseKey(KeyEvent.META_SHIFT_ON);
461             return this;
462         }
463 
ctrl()464         public Builder ctrl() {
465             pressKey(KeyEvent.META_CTRL_ON);
466             return this;
467         }
468 
alt()469         public Builder alt() {
470             pressKey(KeyEvent.META_ALT_ON);
471             return this;
472         }
473 
primary()474         public Builder primary() {
475             pressButton(MotionEvent.BUTTON_PRIMARY);
476             releaseButton(MotionEvent.BUTTON_SECONDARY);
477             releaseButton(MotionEvent.BUTTON_TERTIARY);
478             return this;
479         }
480 
secondary()481         public Builder secondary() {
482             pressButton(MotionEvent.BUTTON_SECONDARY);
483             releaseButton(MotionEvent.BUTTON_PRIMARY);
484             releaseButton(MotionEvent.BUTTON_TERTIARY);
485             return this;
486         }
487 
tertiary()488         public Builder tertiary() {
489             pressButton(MotionEvent.BUTTON_TERTIARY);
490             releaseButton(MotionEvent.BUTTON_PRIMARY);
491             releaseButton(MotionEvent.BUTTON_SECONDARY);
492             return this;
493         }
494 
reset()495         public Builder reset() {
496             mState = new TestEvent();
497             return this;
498         }
499 
500         @Override
clone()501         public Builder clone() {
502             return new Builder(build());
503         }
504 
build()505         public TestEvent build() {
506             // Return a copy, so nobody can mess w/ our internal state.
507             return new TestEvent(mState);
508         }
509     }
510 }
511