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