SystemGesturesPointerEventListener.java revision a4d22d718affbc7145d1012157feb819557b5c06
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.MotionEvent; 26import android.view.WindowManagerPolicy.PointerEventListener; 27import android.widget.OverScroller; 28 29/* 30 * Listens for system-wide input gestures, firing callbacks when detected. 31 * @hide 32 */ 33public class SystemGesturesPointerEventListener implements PointerEventListener { 34 private static final String TAG = "SystemGestures"; 35 private static final boolean DEBUG = false; 36 private static final long SWIPE_TIMEOUT_MS = 500; 37 private static final int MAX_TRACKED_POINTERS = 32; // max per input system 38 private static final int UNTRACKED_POINTER = -1; 39 private static final int MAX_FLING_TIME_MILLIS = 5000; 40 41 private static final int SWIPE_NONE = 0; 42 private static final int SWIPE_FROM_TOP = 1; 43 private static final int SWIPE_FROM_BOTTOM = 2; 44 private static final int SWIPE_FROM_RIGHT = 3; 45 46 private final Context mContext; 47 private final int mSwipeStartThreshold; 48 private final int mSwipeDistanceThreshold; 49 private final Callbacks mCallbacks; 50 private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS]; 51 private final float[] mDownX = new float[MAX_TRACKED_POINTERS]; 52 private final float[] mDownY = new float[MAX_TRACKED_POINTERS]; 53 private final long[] mDownTime = new long[MAX_TRACKED_POINTERS]; 54 55 private GestureDetector mGestureDetector; 56 private OverScroller mOverscroller; 57 58 int screenHeight; 59 int screenWidth; 60 private int mDownPointers; 61 private boolean mSwipeFireable; 62 private boolean mDebugFireable; 63 private long mLastFlingTime; 64 65 public SystemGesturesPointerEventListener(Context context, Callbacks callbacks) { 66 mContext = context; 67 mCallbacks = checkNull("callbacks", callbacks); 68 mSwipeStartThreshold = checkNull("context", context).getResources() 69 .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); 70 mSwipeDistanceThreshold = mSwipeStartThreshold; 71 if (DEBUG) Slog.d(TAG, "mSwipeStartThreshold=" + mSwipeStartThreshold 72 + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold); 73 } 74 75 private static <T> T checkNull(String name, T arg) { 76 if (arg == null) { 77 throw new IllegalArgumentException(name + " must not be null"); 78 } 79 return arg; 80 } 81 82 public void systemReady() { 83 Handler h = new Handler(Looper.myLooper()); 84 mGestureDetector = new GestureDetector(mContext, new FlingGestureDetector(), h); 85 mOverscroller = new OverScroller(mContext); 86 } 87 88 @Override 89 public void onPointerEvent(MotionEvent event) { 90 if (mGestureDetector != null) { 91 mGestureDetector.onTouchEvent(event); 92 } 93 switch (event.getActionMasked()) { 94 case MotionEvent.ACTION_DOWN: 95 mSwipeFireable = true; 96 mDebugFireable = true; 97 mDownPointers = 0; 98 captureDown(event, 0); 99 mCallbacks.onDown(); 100 break; 101 case MotionEvent.ACTION_POINTER_DOWN: 102 captureDown(event, event.getActionIndex()); 103 if (mDebugFireable) { 104 mDebugFireable = event.getPointerCount() < 5; 105 if (!mDebugFireable) { 106 if (DEBUG) Slog.d(TAG, "Firing debug"); 107 mCallbacks.onDebug(); 108 } 109 } 110 break; 111 case MotionEvent.ACTION_MOVE: 112 if (mSwipeFireable) { 113 final int swipe = detectSwipe(event); 114 mSwipeFireable = swipe == SWIPE_NONE; 115 if (swipe == SWIPE_FROM_TOP) { 116 if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop"); 117 mCallbacks.onSwipeFromTop(); 118 } else if (swipe == SWIPE_FROM_BOTTOM) { 119 if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom"); 120 mCallbacks.onSwipeFromBottom(); 121 } else if (swipe == SWIPE_FROM_RIGHT) { 122 if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight"); 123 mCallbacks.onSwipeFromRight(); 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 private final class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener { 215 @Override 216 public boolean onSingleTapUp(MotionEvent e) { 217 if (!mOverscroller.isFinished()) { 218 mOverscroller.forceFinished(true); 219 } 220 return true; 221 } 222 @Override 223 public boolean onFling(MotionEvent down, MotionEvent up, 224 float velocityX, float velocityY) { 225 mOverscroller.computeScrollOffset(); 226 long now = SystemClock.uptimeMillis(); 227 228 if (mLastFlingTime != 0 && now > mLastFlingTime + MAX_FLING_TIME_MILLIS) { 229 mOverscroller.forceFinished(true); 230 } 231 mOverscroller.fling(0, 0, (int)velocityX, (int)velocityY, 232 Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); 233 int duration = mOverscroller.getDuration(); 234 if (duration > MAX_FLING_TIME_MILLIS) { 235 duration = MAX_FLING_TIME_MILLIS; 236 } 237 mLastFlingTime = now; 238 mCallbacks.onFling(duration); 239 return true; 240 } 241 } 242 243 interface Callbacks { 244 void onSwipeFromTop(); 245 void onSwipeFromBottom(); 246 void onSwipeFromRight(); 247 void onFling(int durationMs); 248 void onDown(); 249 void onUpOrCancel(); 250 void onDebug(); 251 } 252} 253