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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 { 115 boolean isMouseEvent(); 116 boolean isPrimaryButtonPressed(); 117 boolean isSecondaryButtonPressed(); 118 boolean isShiftKeyDown(); 119 120 /** Returns true if the action is the initial press of a mouse or touch. */ 121 boolean isActionDown(); 122 123 /** Returns true if the action is the final release of a mouse or touch. */ 124 boolean isActionUp(); 125 126 Point getOrigin(); 127 128 /** Returns true if the there is an item under the finger/cursor. */ 129 boolean isOverItem(); 130 131 /** Returns the adapter position of the item under the finger/cursor. */ 132 int getItemPosition(); 133 } 134 135 public static final class MotionInputEvent implements InputEvent { 136 private final MotionEvent mEvent; 137 private final int mPosition; 138 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 150 public boolean isMouseEvent() { 151 return Events.isMouseEvent(mEvent); 152 } 153 154 @Override 155 public boolean isPrimaryButtonPressed() { 156 return mEvent.isButtonPressed(MotionEvent.BUTTON_PRIMARY); 157 } 158 159 @Override 160 public boolean isSecondaryButtonPressed() { 161 return mEvent.isButtonPressed(MotionEvent.BUTTON_SECONDARY); 162 } 163 164 @Override 165 public boolean isShiftKeyDown() { 166 return Events.hasShiftBit(mEvent.getMetaState()); 167 } 168 169 @Override 170 public boolean isActionDown() { 171 return mEvent.getActionMasked() == MotionEvent.ACTION_DOWN; 172 } 173 174 @Override 175 public boolean isActionUp() { 176 return mEvent.getActionMasked() == MotionEvent.ACTION_UP; 177 } 178 179 @Override 180 public Point getOrigin() { 181 return new Point((int) mEvent.getX(), (int) mEvent.getY()); 182 } 183 184 @Override 185 public boolean isOverItem() { 186 return getItemPosition() != RecyclerView.NO_POSITION; 187 } 188 189 @Override 190 public int getItemPosition() { 191 return mPosition; 192 } 193 194 @Override 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