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 17 package com.android.server.policy; 18 19 import android.content.Context; 20 import android.os.Handler; 21 import android.os.Looper; 22 import android.os.SystemClock; 23 import android.util.Slog; 24 import android.view.GestureDetector; 25 import android.view.InputDevice; 26 import android.view.MotionEvent; 27 import android.view.WindowManagerPolicy.PointerEventListener; 28 import android.widget.OverScroller; 29 30 /* 31 * Listens for system-wide input gestures, firing callbacks when detected. 32 * @hide 33 */ 34 public class SystemGesturesPointerEventListener implements PointerEventListener { 35 private static final String TAG = "SystemGestures"; 36 private static final boolean DEBUG = false; 37 private static final long SWIPE_TIMEOUT_MS = 500; 38 private static final int MAX_TRACKED_POINTERS = 32; // max per input system 39 private static final int UNTRACKED_POINTER = -1; 40 private static final int MAX_FLING_TIME_MILLIS = 5000; 41 42 private static final int SWIPE_NONE = 0; 43 private static final int SWIPE_FROM_TOP = 1; 44 private static final int SWIPE_FROM_BOTTOM = 2; 45 private static final int SWIPE_FROM_RIGHT = 3; 46 private static final int SWIPE_FROM_LEFT = 4; 47 48 private final Context mContext; 49 private final int mSwipeStartThreshold; 50 private final int mSwipeDistanceThreshold; 51 private final Callbacks mCallbacks; 52 private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS]; 53 private final float[] mDownX = new float[MAX_TRACKED_POINTERS]; 54 private final float[] mDownY = new float[MAX_TRACKED_POINTERS]; 55 private final long[] mDownTime = new long[MAX_TRACKED_POINTERS]; 56 57 private GestureDetector mGestureDetector; 58 private OverScroller mOverscroller; 59 60 int screenHeight; 61 int screenWidth; 62 private int mDownPointers; 63 private boolean mSwipeFireable; 64 private boolean mDebugFireable; 65 private boolean mMouseHoveringAtEdge; 66 private long mLastFlingTime; 67 68 public SystemGesturesPointerEventListener(Context context, Callbacks callbacks) { 69 mContext = context; 70 mCallbacks = checkNull("callbacks", callbacks); 71 mSwipeStartThreshold = checkNull("context", context).getResources() 72 .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); 73 mSwipeDistanceThreshold = mSwipeStartThreshold; 74 if (DEBUG) Slog.d(TAG, "mSwipeStartThreshold=" + mSwipeStartThreshold 75 + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold); 76 } 77 78 private static <T> T checkNull(String name, T arg) { 79 if (arg == null) { 80 throw new IllegalArgumentException(name + " must not be null"); 81 } 82 return arg; 83 } 84 85 public void systemReady() { 86 Handler h = new Handler(Looper.myLooper()); 87 mGestureDetector = new GestureDetector(mContext, new FlingGestureDetector(), h); 88 mOverscroller = new OverScroller(mContext); 89 } 90 91 @Override 92 public void onPointerEvent(MotionEvent event) { 93 if (mGestureDetector != null && event.isTouchEvent()) { 94 mGestureDetector.onTouchEvent(event); 95 } 96 switch (event.getActionMasked()) { 97 case MotionEvent.ACTION_DOWN: 98 mSwipeFireable = true; 99 mDebugFireable = true; 100 mDownPointers = 0; 101 captureDown(event, 0); 102 if (mMouseHoveringAtEdge) { 103 mMouseHoveringAtEdge = false; 104 mCallbacks.onMouseLeaveFromEdge(); 105 } 106 mCallbacks.onDown(); 107 break; 108 case MotionEvent.ACTION_POINTER_DOWN: 109 captureDown(event, event.getActionIndex()); 110 if (mDebugFireable) { 111 mDebugFireable = event.getPointerCount() < 5; 112 if (!mDebugFireable) { 113 if (DEBUG) Slog.d(TAG, "Firing debug"); 114 mCallbacks.onDebug(); 115 } 116 } 117 break; 118 case MotionEvent.ACTION_MOVE: 119 if (mSwipeFireable) { 120 final int swipe = detectSwipe(event); 121 mSwipeFireable = swipe == SWIPE_NONE; 122 if (swipe == SWIPE_FROM_TOP) { 123 if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop"); 124 mCallbacks.onSwipeFromTop(); 125 } else if (swipe == SWIPE_FROM_BOTTOM) { 126 if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom"); 127 mCallbacks.onSwipeFromBottom(); 128 } else if (swipe == SWIPE_FROM_RIGHT) { 129 if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight"); 130 mCallbacks.onSwipeFromRight(); 131 } else if (swipe == SWIPE_FROM_LEFT) { 132 if (DEBUG) Slog.d(TAG, "Firing onSwipeFromLeft"); 133 mCallbacks.onSwipeFromLeft(); 134 } 135 } 136 break; 137 case MotionEvent.ACTION_HOVER_MOVE: 138 if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { 139 if (!mMouseHoveringAtEdge && event.getY() == 0) { 140 mCallbacks.onMouseHoverAtTop(); 141 mMouseHoveringAtEdge = true; 142 } else if (!mMouseHoveringAtEdge && event.getY() >= screenHeight - 1) { 143 mCallbacks.onMouseHoverAtBottom(); 144 mMouseHoveringAtEdge = true; 145 } else if (mMouseHoveringAtEdge 146 && (event.getY() > 0 && event.getY() < screenHeight - 1)) { 147 mCallbacks.onMouseLeaveFromEdge(); 148 mMouseHoveringAtEdge = false; 149 } 150 } 151 break; 152 case MotionEvent.ACTION_UP: 153 case MotionEvent.ACTION_CANCEL: 154 mSwipeFireable = false; 155 mDebugFireable = false; 156 mCallbacks.onUpOrCancel(); 157 break; 158 default: 159 if (DEBUG) Slog.d(TAG, "Ignoring " + event); 160 } 161 } 162 163 private void captureDown(MotionEvent event, int pointerIndex) { 164 final int pointerId = event.getPointerId(pointerIndex); 165 final int i = findIndex(pointerId); 166 if (DEBUG) Slog.d(TAG, "pointer " + pointerId + 167 " down pointerIndex=" + pointerIndex + " trackingIndex=" + i); 168 if (i != UNTRACKED_POINTER) { 169 mDownX[i] = event.getX(pointerIndex); 170 mDownY[i] = event.getY(pointerIndex); 171 mDownTime[i] = event.getEventTime(); 172 if (DEBUG) Slog.d(TAG, "pointer " + pointerId + 173 " down x=" + mDownX[i] + " y=" + mDownY[i]); 174 } 175 } 176 177 private int findIndex(int pointerId) { 178 for (int i = 0; i < mDownPointers; i++) { 179 if (mDownPointerId[i] == pointerId) { 180 return i; 181 } 182 } 183 if (mDownPointers == MAX_TRACKED_POINTERS || pointerId == MotionEvent.INVALID_POINTER_ID) { 184 return UNTRACKED_POINTER; 185 } 186 mDownPointerId[mDownPointers++] = pointerId; 187 return mDownPointers - 1; 188 } 189 190 private int detectSwipe(MotionEvent move) { 191 final int historySize = move.getHistorySize(); 192 final int pointerCount = move.getPointerCount(); 193 for (int p = 0; p < pointerCount; p++) { 194 final int pointerId = move.getPointerId(p); 195 final int i = findIndex(pointerId); 196 if (i != UNTRACKED_POINTER) { 197 for (int h = 0; h < historySize; h++) { 198 final long time = move.getHistoricalEventTime(h); 199 final float x = move.getHistoricalX(p, h); 200 final float y = move.getHistoricalY(p, h); 201 final int swipe = detectSwipe(i, time, x, y); 202 if (swipe != SWIPE_NONE) { 203 return swipe; 204 } 205 } 206 final int swipe = detectSwipe(i, move.getEventTime(), move.getX(p), move.getY(p)); 207 if (swipe != SWIPE_NONE) { 208 return swipe; 209 } 210 } 211 } 212 return SWIPE_NONE; 213 } 214 215 private int detectSwipe(int i, long time, float x, float y) { 216 final float fromX = mDownX[i]; 217 final float fromY = mDownY[i]; 218 final long elapsed = time - mDownTime[i]; 219 if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i] 220 + " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed); 221 if (fromY <= mSwipeStartThreshold 222 && y > fromY + mSwipeDistanceThreshold 223 && elapsed < SWIPE_TIMEOUT_MS) { 224 return SWIPE_FROM_TOP; 225 } 226 if (fromY >= screenHeight - mSwipeStartThreshold 227 && y < fromY - mSwipeDistanceThreshold 228 && elapsed < SWIPE_TIMEOUT_MS) { 229 return SWIPE_FROM_BOTTOM; 230 } 231 if (fromX >= screenWidth - mSwipeStartThreshold 232 && x < fromX - mSwipeDistanceThreshold 233 && elapsed < SWIPE_TIMEOUT_MS) { 234 return SWIPE_FROM_RIGHT; 235 } 236 if (fromX <= mSwipeStartThreshold 237 && x > fromX + mSwipeDistanceThreshold 238 && elapsed < SWIPE_TIMEOUT_MS) { 239 return SWIPE_FROM_LEFT; 240 } 241 return SWIPE_NONE; 242 } 243 244 private final class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener { 245 @Override 246 public boolean onSingleTapUp(MotionEvent e) { 247 if (!mOverscroller.isFinished()) { 248 mOverscroller.forceFinished(true); 249 } 250 return true; 251 } 252 @Override 253 public boolean onFling(MotionEvent down, MotionEvent up, 254 float velocityX, float velocityY) { 255 mOverscroller.computeScrollOffset(); 256 long now = SystemClock.uptimeMillis(); 257 258 if (mLastFlingTime != 0 && now > mLastFlingTime + MAX_FLING_TIME_MILLIS) { 259 mOverscroller.forceFinished(true); 260 } 261 mOverscroller.fling(0, 0, (int)velocityX, (int)velocityY, 262 Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); 263 int duration = mOverscroller.getDuration(); 264 if (duration > MAX_FLING_TIME_MILLIS) { 265 duration = MAX_FLING_TIME_MILLIS; 266 } 267 mLastFlingTime = now; 268 mCallbacks.onFling(duration); 269 return true; 270 } 271 } 272 273 interface Callbacks { 274 void onSwipeFromTop(); 275 void onSwipeFromBottom(); 276 void onSwipeFromRight(); 277 void onSwipeFromLeft(); 278 void onFling(int durationMs); 279 void onDown(); 280 void onUpOrCancel(); 281 void onMouseHoverAtTop(); 282 void onMouseHoverAtBottom(); 283 void onMouseLeaveFromEdge(); 284 void onDebug(); 285 } 286 } 287