SystemGesturesPointerEventListener.java revision d2e7e355d836f18adcdb44db9c1931d1c3663ef1
1/* 2 * Copyright (C) 2013 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 com.android.server.policy; 18 19import android.content.Context; 20import android.util.Slog; 21import android.view.MotionEvent; 22import android.view.WindowManagerPolicy.PointerEventListener; 23 24/* 25 * Listens for system-wide input gestures, firing callbacks when detected. 26 * @hide 27 */ 28public class SystemGesturesPointerEventListener implements PointerEventListener { 29 private static final String TAG = "SystemGestures"; 30 private static final boolean DEBUG = false; 31 private static final long SWIPE_TIMEOUT_MS = 500; 32 private static final int MAX_TRACKED_POINTERS = 32; // max per input system 33 private static final int UNTRACKED_POINTER = -1; 34 35 private static final int SWIPE_NONE = 0; 36 private static final int SWIPE_FROM_TOP = 1; 37 private static final int SWIPE_FROM_BOTTOM = 2; 38 private static final int SWIPE_FROM_RIGHT = 3; 39 40 private final int mSwipeStartThreshold; 41 private final int mSwipeDistanceThreshold; 42 private final Callbacks mCallbacks; 43 private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS]; 44 private final float[] mDownX = new float[MAX_TRACKED_POINTERS]; 45 private final float[] mDownY = new float[MAX_TRACKED_POINTERS]; 46 private final long[] mDownTime = new long[MAX_TRACKED_POINTERS]; 47 48 int screenHeight; 49 int screenWidth; 50 private int mDownPointers; 51 private boolean mSwipeFireable; 52 private boolean mDebugFireable; 53 private boolean mMouseHoveringAtEdge; 54 55 public SystemGesturesPointerEventListener(Context context, Callbacks callbacks) { 56 mCallbacks = checkNull("callbacks", callbacks); 57 mSwipeStartThreshold = checkNull("context", context).getResources() 58 .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); 59 mSwipeDistanceThreshold = mSwipeStartThreshold; 60 if (DEBUG) Slog.d(TAG, "mSwipeStartThreshold=" + mSwipeStartThreshold 61 + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold); 62 } 63 64 private static <T> T checkNull(String name, T arg) { 65 if (arg == null) { 66 throw new IllegalArgumentException(name + " must not be null"); 67 } 68 return arg; 69 } 70 71 @Override 72 public void onPointerEvent(MotionEvent event) { 73 switch (event.getActionMasked()) { 74 case MotionEvent.ACTION_DOWN: 75 mSwipeFireable = true; 76 mDebugFireable = true; 77 mDownPointers = 0; 78 captureDown(event, 0); 79 if (mMouseHoveringAtEdge) { 80 mMouseHoveringAtEdge = false; 81 mCallbacks.onMouseLeaveFromEdge(); 82 } 83 mCallbacks.onDown(); 84 break; 85 case MotionEvent.ACTION_POINTER_DOWN: 86 captureDown(event, event.getActionIndex()); 87 if (mDebugFireable) { 88 mDebugFireable = event.getPointerCount() < 5; 89 if (!mDebugFireable) { 90 if (DEBUG) Slog.d(TAG, "Firing debug"); 91 mCallbacks.onDebug(); 92 } 93 } 94 break; 95 case MotionEvent.ACTION_MOVE: 96 if (mSwipeFireable) { 97 final int swipe = detectSwipe(event); 98 mSwipeFireable = swipe == SWIPE_NONE; 99 if (swipe == SWIPE_FROM_TOP) { 100 if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop"); 101 mCallbacks.onSwipeFromTop(); 102 } else if (swipe == SWIPE_FROM_BOTTOM) { 103 if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom"); 104 mCallbacks.onSwipeFromBottom(); 105 } else if (swipe == SWIPE_FROM_RIGHT) { 106 if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight"); 107 mCallbacks.onSwipeFromRight(); 108 } 109 } 110 break; 111 case MotionEvent.ACTION_HOVER_MOVE: 112 if (event.getPointerCount() == 1 113 && event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) { 114 if (!mMouseHoveringAtEdge && event.getY() == 0) { 115 mCallbacks.onMouseHoverAtTop(); 116 mMouseHoveringAtEdge = true; 117 } else if (!mMouseHoveringAtEdge && event.getY() >= screenHeight - 1) { 118 mCallbacks.onMouseHoverAtBottom(); 119 mMouseHoveringAtEdge = true; 120 } else if (mMouseHoveringAtEdge 121 && (event.getY() > 0 && event.getY() < screenHeight - 1)) { 122 mCallbacks.onMouseLeaveFromEdge(); 123 mMouseHoveringAtEdge = false; 124 } 125 } 126 break; 127 case MotionEvent.ACTION_UP: 128 case MotionEvent.ACTION_CANCEL: 129 mSwipeFireable = false; 130 mDebugFireable = false; 131 mCallbacks.onUpOrCancel(); 132 break; 133 default: 134 if (DEBUG) Slog.d(TAG, "Ignoring " + event); 135 } 136 } 137 138 private void captureDown(MotionEvent event, int pointerIndex) { 139 final int pointerId = event.getPointerId(pointerIndex); 140 final int i = findIndex(pointerId); 141 if (DEBUG) Slog.d(TAG, "pointer " + pointerId + 142 " down pointerIndex=" + pointerIndex + " trackingIndex=" + i); 143 if (i != UNTRACKED_POINTER) { 144 mDownX[i] = event.getX(pointerIndex); 145 mDownY[i] = event.getY(pointerIndex); 146 mDownTime[i] = event.getEventTime(); 147 if (DEBUG) Slog.d(TAG, "pointer " + pointerId + 148 " down x=" + mDownX[i] + " y=" + mDownY[i]); 149 } 150 } 151 152 private int findIndex(int pointerId) { 153 for (int i = 0; i < mDownPointers; i++) { 154 if (mDownPointerId[i] == pointerId) { 155 return i; 156 } 157 } 158 if (mDownPointers == MAX_TRACKED_POINTERS || pointerId == MotionEvent.INVALID_POINTER_ID) { 159 return UNTRACKED_POINTER; 160 } 161 mDownPointerId[mDownPointers++] = pointerId; 162 return mDownPointers - 1; 163 } 164 165 private int detectSwipe(MotionEvent move) { 166 final int historySize = move.getHistorySize(); 167 final int pointerCount = move.getPointerCount(); 168 for (int p = 0; p < pointerCount; p++) { 169 final int pointerId = move.getPointerId(p); 170 final int i = findIndex(pointerId); 171 if (i != UNTRACKED_POINTER) { 172 for (int h = 0; h < historySize; h++) { 173 final long time = move.getHistoricalEventTime(h); 174 final float x = move.getHistoricalX(p, h); 175 final float y = move.getHistoricalY(p, h); 176 final int swipe = detectSwipe(i, time, x, y); 177 if (swipe != SWIPE_NONE) { 178 return swipe; 179 } 180 } 181 final int swipe = detectSwipe(i, move.getEventTime(), move.getX(p), move.getY(p)); 182 if (swipe != SWIPE_NONE) { 183 return swipe; 184 } 185 } 186 } 187 return SWIPE_NONE; 188 } 189 190 private int detectSwipe(int i, long time, float x, float y) { 191 final float fromX = mDownX[i]; 192 final float fromY = mDownY[i]; 193 final long elapsed = time - mDownTime[i]; 194 if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i] 195 + " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed); 196 if (fromY <= mSwipeStartThreshold 197 && y > fromY + mSwipeDistanceThreshold 198 && elapsed < SWIPE_TIMEOUT_MS) { 199 return SWIPE_FROM_TOP; 200 } 201 if (fromY >= screenHeight - mSwipeStartThreshold 202 && y < fromY - mSwipeDistanceThreshold 203 && elapsed < SWIPE_TIMEOUT_MS) { 204 return SWIPE_FROM_BOTTOM; 205 } 206 if (fromX >= screenWidth - mSwipeStartThreshold 207 && x < fromX - mSwipeDistanceThreshold 208 && elapsed < SWIPE_TIMEOUT_MS) { 209 return SWIPE_FROM_RIGHT; 210 } 211 return SWIPE_NONE; 212 } 213 214 interface Callbacks { 215 void onSwipeFromTop(); 216 void onSwipeFromBottom(); 217 void onSwipeFromRight(); 218 void onDown(); 219 void onUpOrCancel(); 220 void onMouseHoverAtTop(); 221 void onMouseHoverAtBottom(); 222 void onMouseLeaveFromEdge(); 223 void onDebug(); 224 } 225} 226