Home | History | Annotate | Download | only in testing
      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 
     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 
     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
    107     public Point getOrigin() {
    108         return mLocation;
    109     }
    110 
    111     @Override
    112     public float getX() {
    113         return mLocation.x;
    114     }
    115 
    116     @Override
    117     public float getY() {
    118         return mLocation.y;
    119     }
    120 
    121     @Override
    122     public float getRawX() {
    123         return mRawLocation.x;
    124     }
    125 
    126     @Override
    127     public float getRawY() {
    128         return mRawLocation.y;
    129     }
    130 
    131     @Override
    132     public int getPointerCount() {
    133         return mPointerCount;
    134     }
    135 
    136     @Override
    137     public boolean isMouseEvent() {
    138         return mToolType == MotionEvent.TOOL_TYPE_MOUSE;
    139     }
    140 
    141     @Override
    142     public boolean isPrimaryButtonPressed() {
    143         return mButtons.contains(MotionEvent.BUTTON_PRIMARY);
    144     }
    145 
    146     @Override
    147     public boolean isSecondaryButtonPressed() {
    148         return mButtons.contains(MotionEvent.BUTTON_SECONDARY);
    149     }
    150 
    151     @Override
    152     public boolean isTertiaryButtonPressed() {
    153         return mButtons.contains(MotionEvent.BUTTON_TERTIARY);
    154     }
    155 
    156     @Override
    157     public boolean isShiftKeyDown() {
    158         return mKeys.contains(KeyEvent.META_SHIFT_ON);
    159     }
    160 
    161     @Override
    162     public boolean isCtrlKeyDown() {
    163         return mKeys.contains(KeyEvent.META_CTRL_ON);
    164     }
    165 
    166     @Override
    167     public boolean isAltKeyDown() {
    168         return mKeys.contains(KeyEvent.META_ALT_ON);
    169     }
    170 
    171     @Override
    172     public boolean isActionDown() {
    173         return mAction == MotionEvent.ACTION_DOWN;
    174     }
    175 
    176     @Override
    177     public boolean isActionUp() {
    178         return mAction == MotionEvent.ACTION_UP;
    179     }
    180 
    181     @Override
    182     public boolean isMultiPointerActionDown() {
    183         return mAction == MotionEvent.ACTION_POINTER_DOWN;
    184     }
    185 
    186     @Override
    187     public boolean isMultiPointerActionUp() {
    188         return mAction == MotionEvent.ACTION_POINTER_UP;
    189     }
    190 
    191     @Override
    192     public boolean isActionMove() {
    193         return mAction == MotionEvent.ACTION_MOVE;
    194     }
    195 
    196     @Override
    197     public boolean isActionCancel() {
    198         return mAction == MotionEvent.ACTION_CANCEL;
    199     }
    200 
    201     @Override
    202     public boolean isOverItem() {
    203         return mDetails.isOverItem();
    204     }
    205 
    206     @Override
    207     public boolean isOverDocIcon() {
    208         return mDetails.isOverDocIcon(this);
    209     }
    210 
    211     @Override
    212     public boolean isOverDragHotspot() {
    213         return isOverItem() && mDetails.isInDragHotspot(this);
    214     }
    215 
    216     @Override
    217     public boolean isOverModelItem() {
    218         if (isOverItem()) {
    219             DocumentDetails doc = getDocumentDetails();
    220             return doc != null && doc.hasModelId();
    221         }
    222         return false;
    223     }
    224 
    225     @Override
    226     public boolean isTouchpadScroll() {
    227         return isMouseEvent() && mButtons.isEmpty() && isActionMove();
    228     }
    229 
    230     @Override
    231     public int getItemPosition() {
    232         return mDetails.mPosition;
    233     }
    234 
    235     @Override
    236     public DocumentDetails getDocumentDetails() {
    237         return mDetails;
    238     }
    239 
    240     @Override
    241     public void close() {}
    242 
    243     @Override
    244     public int hashCode() {
    245         return mDetails.hashCode();
    246     }
    247 
    248     @Override
    249     public boolean equals(Object o) {
    250       if (this == o) {
    251           return true;
    252       }
    253 
    254       if (!(o instanceof TestEvent)) {
    255           return false;
    256       }
    257 
    258       TestEvent other = (TestEvent) o;
    259       return mAction == other.mAction
    260               && mToolType == other.mToolType
    261               && mButtons.equals(other.mButtons)
    262               && mKeys.equals(other.mKeys)
    263               && mLocation.equals(other.mLocation)
    264               && mRawLocation.equals(other.mRawLocation)
    265               && mDetails.equals(other.mDetails);
    266     }
    267 
    268     private static final class Details implements DocumentDetails {
    269 
    270         private int mPosition;
    271         private String mModelId;
    272         private boolean mInSelectionHotspot;
    273         private boolean mInDragHotspot;
    274         private boolean mOverDocIcon;
    275 
    276         public Details() {
    277            mPosition = Integer.MIN_VALUE;
    278         }
    279 
    280         public Details(Details source) {
    281             mPosition = source.mPosition;
    282             mModelId = source.mModelId;
    283             mInSelectionHotspot = source.mInSelectionHotspot;
    284             mInDragHotspot = source.mInDragHotspot;
    285             mOverDocIcon = source.mOverDocIcon;
    286         }
    287 
    288 
    289         private boolean isOverItem() {
    290             return mPosition != Integer.MIN_VALUE && mPosition != RecyclerView.NO_POSITION;
    291         }
    292 
    293         @Override
    294         public boolean hasModelId() {
    295             return !TextUtils.isEmpty(mModelId);
    296         }
    297 
    298         @Override
    299         public String getModelId() {
    300             return mModelId;
    301         }
    302 
    303         @Override
    304         public int getAdapterPosition() {
    305             return mPosition;
    306         }
    307 
    308         @Override
    309         public boolean isInSelectionHotspot(InputEvent event) {
    310             return mInSelectionHotspot;
    311         }
    312 
    313         @Override
    314         public boolean isInDragHotspot(InputEvent event) {
    315             return mInDragHotspot;
    316         }
    317 
    318         @Override
    319         public boolean isOverDocIcon(InputEvent event) {
    320             return mOverDocIcon;
    321         }
    322 
    323         @Override
    324         public int hashCode() {
    325             return mModelId != null ? mModelId.hashCode() : ACTION_UNSET;
    326         }
    327 
    328         @Override
    329         public boolean equals(Object o) {
    330           if (this == o) {
    331               return true;
    332           }
    333 
    334           if (!(o instanceof Details)) {
    335               return false;
    336           }
    337 
    338           Details other = (Details) o;
    339           return mPosition == other.mPosition
    340                   && mModelId == other.mModelId;
    341         }
    342     }
    343 
    344     public static final Builder builder() {
    345         return new Builder();
    346     }
    347 
    348     /**
    349      * Test event builder with convenience methods for common event attrs.
    350      */
    351     public static final class Builder {
    352 
    353         private TestEvent mState = new TestEvent();
    354 
    355         public Builder() {
    356         }
    357 
    358         public Builder(TestEvent state) {
    359             mState = new TestEvent(state);
    360         }
    361 
    362         /**
    363          * @param action Any action specified in {@link MotionEvent}.
    364          * @return
    365          */
    366         public Builder action(int action) {
    367             mState.mAction = action;
    368             return this;
    369         }
    370 
    371         public Builder type(@ToolType int type) {
    372             mState.mToolType = type;
    373             return this;
    374         }
    375 
    376         public Builder location(int x, int y) {
    377             mState.mLocation = new Point(x, y);
    378             return this;
    379         }
    380 
    381         public Builder rawLocation(int x, int y) {
    382             mState.mRawLocation = new Point(x, y);
    383             return this;
    384         }
    385 
    386         public Builder pointerCount(int count) {
    387             mState.mPointerCount = count;
    388             return this;
    389         }
    390 
    391         /**
    392          * Adds one or more button press attributes.
    393          */
    394         public Builder pressButton(@Button int... buttons) {
    395             for (int button : buttons) {
    396                 mState.mButtons.add(button);
    397             }
    398             return this;
    399         }
    400 
    401         /**
    402          * Removes one or more button press attributes.
    403          */
    404         public Builder releaseButton(@Button int... buttons) {
    405             for (int button : buttons) {
    406                 mState.mButtons.remove(button);
    407             }
    408             return this;
    409         }
    410 
    411         /**
    412          * Adds one or more key press attributes.
    413          */
    414         public Builder pressKey(@Key int... keys) {
    415             for (int key : keys) {
    416                 mState.mKeys.add(key);
    417             }
    418             return this;
    419         }
    420 
    421         /**
    422          * Removes one or more key press attributes.
    423          */
    424         public Builder releaseKey(@Button int... keys) {
    425             for (int key : keys) {
    426                 mState.mKeys.remove(key);
    427             }
    428             return this;
    429         }
    430 
    431         public Builder at(int position) {
    432             mState.mDetails.mPosition = position;  // this is both "adapter position" and "item position".
    433             mState.mDetails.mModelId = String.valueOf(position);
    434             return this;
    435         }
    436 
    437         public Builder inSelectionHotspot() {
    438             mState.mDetails.mInSelectionHotspot = true;
    439             return this;
    440         }
    441 
    442         public Builder inDragHotspot() {
    443             mState.mDetails.mInDragHotspot = true;
    444             return this;
    445         }
    446 
    447         public Builder notInDragHotspot() {
    448             mState.mDetails.mInDragHotspot = false;
    449             return this;
    450         }
    451 
    452         public Builder overDocIcon() {
    453             mState.mDetails.mOverDocIcon = true;
    454             return this;
    455         }
    456 
    457         public Builder notOverDocIcon() {
    458             mState.mDetails.mOverDocIcon = false;
    459             return this;
    460         }
    461 
    462         public Builder touch() {
    463             type(MotionEvent.TOOL_TYPE_FINGER);
    464             return this;
    465         }
    466 
    467         public Builder mouse() {
    468             type(MotionEvent.TOOL_TYPE_MOUSE);
    469             return this;
    470         }
    471 
    472         public Builder shift() {
    473             pressKey(KeyEvent.META_SHIFT_ON);
    474             return this;
    475         }
    476 
    477         /**
    478          * Use {@link #remove(@Attribute int...)}
    479          */
    480         @Deprecated
    481         public Builder unshift() {
    482             releaseKey(KeyEvent.META_SHIFT_ON);
    483             return this;
    484         }
    485 
    486         public Builder ctrl() {
    487             pressKey(KeyEvent.META_CTRL_ON);
    488             return this;
    489         }
    490 
    491         public Builder alt() {
    492             pressKey(KeyEvent.META_ALT_ON);
    493             return this;
    494         }
    495 
    496         public Builder primary() {
    497             pressButton(MotionEvent.BUTTON_PRIMARY);
    498             releaseButton(MotionEvent.BUTTON_SECONDARY);
    499             releaseButton(MotionEvent.BUTTON_TERTIARY);
    500             return this;
    501         }
    502 
    503         public Builder secondary() {
    504             pressButton(MotionEvent.BUTTON_SECONDARY);
    505             releaseButton(MotionEvent.BUTTON_PRIMARY);
    506             releaseButton(MotionEvent.BUTTON_TERTIARY);
    507             return this;
    508         }
    509 
    510         public Builder tertiary() {
    511             pressButton(MotionEvent.BUTTON_TERTIARY);
    512             releaseButton(MotionEvent.BUTTON_PRIMARY);
    513             releaseButton(MotionEvent.BUTTON_SECONDARY);
    514             return this;
    515         }
    516 
    517         public Builder reset() {
    518             mState = new TestEvent();
    519             return this;
    520         }
    521 
    522         @Override
    523         public Builder clone() {
    524             return new Builder(build());
    525         }
    526 
    527         public TestEvent build() {
    528             // Return a copy, so nobody can mess w/ our internal state.
    529             return new TestEvent(mState);
    530         }
    531     }
    532 }
    533