1 /*
2  * Copyright (C) 2015 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;
18 
19 import android.graphics.Point;
20 import android.support.v7.widget.RecyclerView;
21 import android.view.KeyEvent;
22 import android.view.MotionEvent;
23 import android.view.View;
24 
25 /**
26  * Utility code for dealing with MotionEvents.
27  */
28 public final class Events {
29 
30     /**
31      * Returns true if event was triggered by a mouse.
32      */
isMouseEvent(MotionEvent e)33     public static boolean isMouseEvent(MotionEvent e) {
34         return isMouseType(e.getToolType(0));
35     }
36 
37     /**
38      * Returns true if event was triggered by a finger or stylus touch.
39      */
isTouchEvent(MotionEvent e)40     public static boolean isTouchEvent(MotionEvent e) {
41         return isTouchType(e.getToolType(0));
42     }
43 
44     /**
45      * Returns true if event was triggered by a mouse.
46      */
isMouseType(int toolType)47     public static boolean isMouseType(int toolType) {
48         return toolType == MotionEvent.TOOL_TYPE_MOUSE;
49     }
50 
51     /**
52      * Returns true if event was triggered by a finger or stylus touch.
53      */
isTouchType(int toolType)54     public static boolean isTouchType(int toolType) {
55         return toolType == MotionEvent.TOOL_TYPE_FINGER
56                 || toolType == MotionEvent.TOOL_TYPE_STYLUS;
57     }
58 
59     /**
60      * Returns true if event was triggered by a finger or stylus touch.
61      */
isActionDown(MotionEvent e)62     public static boolean isActionDown(MotionEvent e) {
63         return e.getActionMasked() == MotionEvent.ACTION_DOWN;
64     }
65 
66     /**
67      * Returns true if event was triggered by a finger or stylus touch.
68      */
isActionUp(MotionEvent e)69     public static boolean isActionUp(MotionEvent e) {
70         return e.getActionMasked() == MotionEvent.ACTION_UP;
71     }
72 
73     /**
74      * Returns true if the shift is pressed.
75      */
isShiftPressed(MotionEvent e)76     public boolean isShiftPressed(MotionEvent e) {
77         return hasShiftBit(e.getMetaState());
78     }
79 
80     /**
81      * Whether or not the given keyCode represents a navigation keystroke (e.g. up, down, home).
82      *
83      * @param keyCode
84      * @return
85      */
isNavigationKeyCode(int keyCode)86     public static boolean isNavigationKeyCode(int keyCode) {
87         switch (keyCode) {
88             case KeyEvent.KEYCODE_DPAD_UP:
89             case KeyEvent.KEYCODE_DPAD_DOWN:
90             case KeyEvent.KEYCODE_DPAD_LEFT:
91             case KeyEvent.KEYCODE_DPAD_RIGHT:
92             case KeyEvent.KEYCODE_MOVE_HOME:
93             case KeyEvent.KEYCODE_MOVE_END:
94             case KeyEvent.KEYCODE_PAGE_UP:
95             case KeyEvent.KEYCODE_PAGE_DOWN:
96                 return true;
97             default:
98                 return false;
99         }
100     }
101 
102 
103     /**
104      * Returns true if the "SHIFT" bit is set.
105      */
hasShiftBit(int metaState)106     public static boolean hasShiftBit(int metaState) {
107         return (metaState & KeyEvent.META_SHIFT_ON) != 0;
108     }
109 
110     /**
111      * A facade over MotionEvent primarily designed to permit for unit testing
112      * of related code.
113      */
114     public interface InputEvent {
isMouseEvent()115         boolean isMouseEvent();
isPrimaryButtonPressed()116         boolean isPrimaryButtonPressed();
isSecondaryButtonPressed()117         boolean isSecondaryButtonPressed();
isShiftKeyDown()118         boolean isShiftKeyDown();
119 
120         /** Returns true if the action is the initial press of a mouse or touch. */
isActionDown()121         boolean isActionDown();
122 
123         /** Returns true if the action is the final release of a mouse or touch. */
isActionUp()124         boolean isActionUp();
125 
getOrigin()126         Point getOrigin();
127 
128         /** Returns true if the there is an item under the finger/cursor. */
isOverItem()129         boolean isOverItem();
130 
131         /** Returns the adapter position of the item under the finger/cursor. */
getItemPosition()132         int getItemPosition();
133     }
134 
135     public static final class MotionInputEvent implements InputEvent {
136         private final MotionEvent mEvent;
137         private final int mPosition;
138 
MotionInputEvent(MotionEvent event, RecyclerView view)139         public MotionInputEvent(MotionEvent event, RecyclerView view) {
140             mEvent = event;
141 
142             // Consider determining position lazily as an optimization.
143             View child = view.findChildViewUnder(mEvent.getX(), mEvent.getY());
144             mPosition = (child!= null)
145                     ? view.getChildAdapterPosition(child)
146                     : RecyclerView.NO_POSITION;
147         }
148 
149         @Override
isMouseEvent()150         public boolean isMouseEvent() {
151             return Events.isMouseEvent(mEvent);
152         }
153 
154         @Override
isPrimaryButtonPressed()155         public boolean isPrimaryButtonPressed() {
156             return mEvent.isButtonPressed(MotionEvent.BUTTON_PRIMARY);
157         }
158 
159         @Override
isSecondaryButtonPressed()160         public boolean isSecondaryButtonPressed() {
161             return mEvent.isButtonPressed(MotionEvent.BUTTON_SECONDARY);
162         }
163 
164         @Override
isShiftKeyDown()165         public boolean isShiftKeyDown() {
166             return Events.hasShiftBit(mEvent.getMetaState());
167         }
168 
169         @Override
isActionDown()170         public boolean isActionDown() {
171             return mEvent.getActionMasked() == MotionEvent.ACTION_DOWN;
172         }
173 
174         @Override
isActionUp()175         public boolean isActionUp() {
176             return mEvent.getActionMasked() == MotionEvent.ACTION_UP;
177         }
178 
179         @Override
getOrigin()180         public Point getOrigin() {
181             return new Point((int) mEvent.getX(), (int) mEvent.getY());
182         }
183 
184         @Override
isOverItem()185         public boolean isOverItem() {
186             return getItemPosition() != RecyclerView.NO_POSITION;
187         }
188 
189         @Override
getItemPosition()190         public int getItemPosition() {
191             return mPosition;
192         }
193 
194         @Override
toString()195         public String toString() {
196             return new StringBuilder()
197                     .append("MotionInputEvent {")
198                     .append("isMouseEvent=").append(isMouseEvent())
199                     .append(" isPrimaryButtonPressed=").append(isPrimaryButtonPressed())
200                     .append(" isSecondaryButtonPressed=").append(isSecondaryButtonPressed())
201                     .append(" isShiftKeyDown=").append(isShiftKeyDown())
202                     .append(" isActionDown=").append(isActionDown())
203                     .append(" isActionUp=").append(isActionUp())
204                     .append(" getOrigin=").append(getOrigin())
205                     .append(" isOverItem=").append(isOverItem())
206                     .append(" getItemPosition=").append(getItemPosition())
207                     .append("}")
208                     .toString();
209         }
210     }
211 }
212