blob: 80e43417f6478ff766fa539d0f49b0d4bff772ef [file] [log] [blame]
John Spurlock57306e62013-04-22 09:48:49 -04001/*
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
Jorim Jaggib10e33f2015-02-04 21:57:40 +010017package com.android.server.policy;
John Spurlock57306e62013-04-22 09:48:49 -040018
19import android.content.Context;
Michael Wrighta4d22d72015-09-16 23:19:26 +010020import android.os.Handler;
21import android.os.Looper;
22import android.os.SystemClock;
John Spurlock57306e62013-04-22 09:48:49 -040023import android.util.Slog;
Michael Wrighta4d22d72015-09-16 23:19:26 +010024import android.view.GestureDetector;
Jun Mukai99465292015-10-01 18:42:33 +090025import android.view.InputDevice;
John Spurlock57306e62013-04-22 09:48:49 -040026import android.view.MotionEvent;
Craig Mautner037aa8d2013-06-07 10:35:44 -070027import android.view.WindowManagerPolicy.PointerEventListener;
Michael Wrighta4d22d72015-09-16 23:19:26 +010028import android.widget.OverScroller;
John Spurlock57306e62013-04-22 09:48:49 -040029
30/*
31 * Listens for system-wide input gestures, firing callbacks when detected.
32 * @hide
33 */
Craig Mautner037aa8d2013-06-07 10:35:44 -070034public class SystemGesturesPointerEventListener implements PointerEventListener {
John Spurlock57306e62013-04-22 09:48:49 -040035 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;
Michael Wrighta4d22d72015-09-16 23:19:26 +010040 private static final int MAX_FLING_TIME_MILLIS = 5000;
John Spurlock57306e62013-04-22 09:48:49 -040041
John Spurlockad3e6cb2013-04-30 08:47:43 -040042 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
Michael Wrighta4d22d72015-09-16 23:19:26 +010047 private final Context mContext;
John Spurlock57306e62013-04-22 09:48:49 -040048 private final int mSwipeStartThreshold;
John Spurlock9ba21fd2013-07-29 08:57:32 -040049 private final int mSwipeDistanceThreshold;
John Spurlock57306e62013-04-22 09:48:49 -040050 private final Callbacks mCallbacks;
51 private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS];
John Spurlockad3e6cb2013-04-30 08:47:43 -040052 private final float[] mDownX = new float[MAX_TRACKED_POINTERS];
John Spurlock57306e62013-04-22 09:48:49 -040053 private final float[] mDownY = new float[MAX_TRACKED_POINTERS];
54 private final long[] mDownTime = new long[MAX_TRACKED_POINTERS];
55
Michael Wrighta4d22d72015-09-16 23:19:26 +010056 private GestureDetector mGestureDetector;
57 private OverScroller mOverscroller;
58
John Spurlockad3e6cb2013-04-30 08:47:43 -040059 int screenHeight;
60 int screenWidth;
John Spurlock57306e62013-04-22 09:48:49 -040061 private int mDownPointers;
John Spurlockad3e6cb2013-04-30 08:47:43 -040062 private boolean mSwipeFireable;
63 private boolean mDebugFireable;
Jun Mukaid2e7e352015-07-22 17:14:02 -070064 private boolean mMouseHoveringAtEdge;
Michael Wrighta4d22d72015-09-16 23:19:26 +010065 private long mLastFlingTime;
John Spurlock57306e62013-04-22 09:48:49 -040066
Craig Mautner037aa8d2013-06-07 10:35:44 -070067 public SystemGesturesPointerEventListener(Context context, Callbacks callbacks) {
Michael Wrighta4d22d72015-09-16 23:19:26 +010068 mContext = context;
John Spurlock57306e62013-04-22 09:48:49 -040069 mCallbacks = checkNull("callbacks", callbacks);
70 mSwipeStartThreshold = checkNull("context", context).getResources()
71 .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
John Spurlock9ba21fd2013-07-29 08:57:32 -040072 mSwipeDistanceThreshold = mSwipeStartThreshold;
73 if (DEBUG) Slog.d(TAG, "mSwipeStartThreshold=" + mSwipeStartThreshold
74 + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold);
John Spurlock57306e62013-04-22 09:48:49 -040075 }
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
Michael Wrighta4d22d72015-09-16 23:19:26 +010084 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
John Spurlock57306e62013-04-22 09:48:49 -040090 @Override
Craig Mautner037aa8d2013-06-07 10:35:44 -070091 public void onPointerEvent(MotionEvent event) {
Dennis Kempinfb5168a2016-01-04 17:06:53 -080092 if (mGestureDetector != null && event.isTouchEvent()) {
Michael Wrighta4d22d72015-09-16 23:19:26 +010093 mGestureDetector.onTouchEvent(event);
94 }
John Spurlock57306e62013-04-22 09:48:49 -040095 switch (event.getActionMasked()) {
96 case MotionEvent.ACTION_DOWN:
John Spurlockad3e6cb2013-04-30 08:47:43 -040097 mSwipeFireable = true;
98 mDebugFireable = true;
John Spurlock57306e62013-04-22 09:48:49 -040099 mDownPointers = 0;
100 captureDown(event, 0);
Jun Mukaid2e7e352015-07-22 17:14:02 -0700101 if (mMouseHoveringAtEdge) {
102 mMouseHoveringAtEdge = false;
103 mCallbacks.onMouseLeaveFromEdge();
104 }
Adrian Roos3595be42015-03-05 16:31:15 +0100105 mCallbacks.onDown();
John Spurlock57306e62013-04-22 09:48:49 -0400106 break;
107 case MotionEvent.ACTION_POINTER_DOWN:
108 captureDown(event, event.getActionIndex());
John Spurlockad3e6cb2013-04-30 08:47:43 -0400109 if (mDebugFireable) {
110 mDebugFireable = event.getPointerCount() < 5;
111 if (!mDebugFireable) {
112 if (DEBUG) Slog.d(TAG, "Firing debug");
113 mCallbacks.onDebug();
114 }
115 }
John Spurlock57306e62013-04-22 09:48:49 -0400116 break;
117 case MotionEvent.ACTION_MOVE:
John Spurlockad3e6cb2013-04-30 08:47:43 -0400118 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 }
John Spurlock57306e62013-04-22 09:48:49 -0400131 }
132 break;
Jun Mukaid2e7e352015-07-22 17:14:02 -0700133 case MotionEvent.ACTION_HOVER_MOVE:
Jun Mukaic5bd9bc2015-10-15 02:56:15 -0700134 if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
Jun Mukaid2e7e352015-07-22 17:14:02 -0700135 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;
John Spurlock57306e62013-04-22 09:48:49 -0400148 case MotionEvent.ACTION_UP:
149 case MotionEvent.ACTION_CANCEL:
John Spurlockad3e6cb2013-04-30 08:47:43 -0400150 mSwipeFireable = false;
151 mDebugFireable = false;
Adrian Roos3595be42015-03-05 16:31:15 +0100152 mCallbacks.onUpOrCancel();
John Spurlock57306e62013-04-22 09:48:49 -0400153 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) {
John Spurlockad3e6cb2013-04-30 08:47:43 -0400165 mDownX[i] = event.getX(pointerIndex);
John Spurlock57306e62013-04-22 09:48:49 -0400166 mDownY[i] = event.getY(pointerIndex);
167 mDownTime[i] = event.getEventTime();
John Spurlockad3e6cb2013-04-30 08:47:43 -0400168 if (DEBUG) Slog.d(TAG, "pointer " + pointerId +
169 " down x=" + mDownX[i] + " y=" + mDownY[i]);
John Spurlock57306e62013-04-22 09:48:49 -0400170 }
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
John Spurlockad3e6cb2013-04-30 08:47:43 -0400186 private int detectSwipe(MotionEvent move) {
John Spurlock57306e62013-04-22 09:48:49 -0400187 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);
John Spurlockad3e6cb2013-04-30 08:47:43 -0400195 final float x = move.getHistoricalX(p, h);
John Spurlock57306e62013-04-22 09:48:49 -0400196 final float y = move.getHistoricalY(p, h);
John Spurlockad3e6cb2013-04-30 08:47:43 -0400197 final int swipe = detectSwipe(i, time, x, y);
198 if (swipe != SWIPE_NONE) {
199 return swipe;
John Spurlock57306e62013-04-22 09:48:49 -0400200 }
201 }
John Spurlockad3e6cb2013-04-30 08:47:43 -0400202 final int swipe = detectSwipe(i, move.getEventTime(), move.getX(p), move.getY(p));
203 if (swipe != SWIPE_NONE) {
204 return swipe;
John Spurlock57306e62013-04-22 09:48:49 -0400205 }
206 }
207 }
John Spurlockad3e6cb2013-04-30 08:47:43 -0400208 return SWIPE_NONE;
John Spurlock57306e62013-04-22 09:48:49 -0400209 }
210
John Spurlockad3e6cb2013-04-30 08:47:43 -0400211 private int detectSwipe(int i, long time, float x, float y) {
212 final float fromX = mDownX[i];
John Spurlock57306e62013-04-22 09:48:49 -0400213 final float fromY = mDownY[i];
214 final long elapsed = time - mDownTime[i];
215 if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i]
John Spurlockad3e6cb2013-04-30 08:47:43 -0400216 + " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed);
217 if (fromY <= mSwipeStartThreshold
John Spurlock9ba21fd2013-07-29 08:57:32 -0400218 && y > fromY + mSwipeDistanceThreshold
John Spurlockad3e6cb2013-04-30 08:47:43 -0400219 && elapsed < SWIPE_TIMEOUT_MS) {
220 return SWIPE_FROM_TOP;
221 }
222 if (fromY >= screenHeight - mSwipeStartThreshold
John Spurlock9ba21fd2013-07-29 08:57:32 -0400223 && y < fromY - mSwipeDistanceThreshold
John Spurlockad3e6cb2013-04-30 08:47:43 -0400224 && elapsed < SWIPE_TIMEOUT_MS) {
225 return SWIPE_FROM_BOTTOM;
226 }
227 if (fromX >= screenWidth - mSwipeStartThreshold
John Spurlock9ba21fd2013-07-29 08:57:32 -0400228 && x < fromX - mSwipeDistanceThreshold
John Spurlockad3e6cb2013-04-30 08:47:43 -0400229 && elapsed < SWIPE_TIMEOUT_MS) {
230 return SWIPE_FROM_RIGHT;
231 }
232 return SWIPE_NONE;
John Spurlock57306e62013-04-22 09:48:49 -0400233 }
234
Michael Wrighta4d22d72015-09-16 23:19:26 +0100235 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
John Spurlock57306e62013-04-22 09:48:49 -0400264 interface Callbacks {
265 void onSwipeFromTop();
John Spurlockad3e6cb2013-04-30 08:47:43 -0400266 void onSwipeFromBottom();
267 void onSwipeFromRight();
Michael Wrighta4d22d72015-09-16 23:19:26 +0100268 void onFling(int durationMs);
Adrian Roos3595be42015-03-05 16:31:15 +0100269 void onDown();
270 void onUpOrCancel();
Jun Mukaid2e7e352015-07-22 17:14:02 -0700271 void onMouseHoverAtTop();
272 void onMouseHoverAtBottom();
273 void onMouseLeaveFromEdge();
John Spurlockad3e6cb2013-04-30 08:47:43 -0400274 void onDebug();
John Spurlock57306e62013-04-22 09:48:49 -0400275 }
276}