blob: e3f65ef812fb2d953001c4b0b7606acdeb88a9f0 [file] [log] [blame]
Winson Chungfa7053782016-11-08 15:45:10 -08001/*
2 * Copyright (C) 2016 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.systemui.pip.phone;
18
Winson Chungfa7053782016-11-08 15:45:10 -080019import android.graphics.PointF;
Winson Chungbca03112017-08-16 10:38:15 -070020import android.os.Handler;
Winson Chung040e7222017-03-21 10:47:09 -070021import android.util.Log;
Winson Chungfa7053782016-11-08 15:45:10 -080022import android.view.MotionEvent;
23import android.view.VelocityTracker;
24import android.view.ViewConfiguration;
25
Winson Chungbca03112017-08-16 10:38:15 -070026import com.android.internal.annotations.VisibleForTesting;
27
Winson Chung29a78652017-02-09 18:35:26 -080028import java.io.PrintWriter;
29
Winson Chungfa7053782016-11-08 15:45:10 -080030/**
31 * This keeps track of the touch state throughout the current touch gesture.
32 */
33public class PipTouchState {
Winson Chung29a78652017-02-09 18:35:26 -080034 private static final String TAG = "PipTouchHandler";
Winson Chung427cec42017-07-17 11:35:39 -070035 private static final boolean DEBUG = false;
Winson Chungfa7053782016-11-08 15:45:10 -080036
Winson Chungbca03112017-08-16 10:38:15 -070037 @VisibleForTesting
38 static final long DOUBLE_TAP_TIMEOUT = 200;
39
40 private final Handler mHandler;
41 private final ViewConfiguration mViewConfig;
42 private final Runnable mDoubleTapTimeoutCallback;
Winson Chungfa7053782016-11-08 15:45:10 -080043
44 private VelocityTracker mVelocityTracker;
Winson Chungbca03112017-08-16 10:38:15 -070045 private long mDownTouchTime = 0;
46 private long mLastDownTouchTime = 0;
47 private long mUpTouchTime = 0;
Winson Chungfa7053782016-11-08 15:45:10 -080048 private final PointF mDownTouch = new PointF();
49 private final PointF mDownDelta = new PointF();
50 private final PointF mLastTouch = new PointF();
51 private final PointF mLastDelta = new PointF();
52 private final PointF mVelocity = new PointF();
Winson Chung85d39982017-02-24 15:21:25 -080053 private boolean mAllowTouches = true;
Winson Chung2a82fe52017-02-02 14:43:34 -080054 private boolean mIsUserInteracting = false;
Winson Chungbca03112017-08-16 10:38:15 -070055 // Set to true only if the multiple taps occur within the double tap timeout
56 private boolean mIsDoubleTap = false;
57 // Set to true only if a gesture
58 private boolean mIsWaitingForDoubleTap = false;
Winson Chungfa7053782016-11-08 15:45:10 -080059 private boolean mIsDragging = false;
Winson Chungbca03112017-08-16 10:38:15 -070060 // The previous gesture was a drag
61 private boolean mPreviouslyDragging = false;
Winson Chungfa7053782016-11-08 15:45:10 -080062 private boolean mStartedDragging = false;
Winson Chungd5a01592016-11-11 16:25:04 -080063 private boolean mAllowDraggingOffscreen = false;
Winson Chungfa7053782016-11-08 15:45:10 -080064 private int mActivePointerId;
65
Winson Chungbca03112017-08-16 10:38:15 -070066 public PipTouchState(ViewConfiguration viewConfig, Handler handler,
67 Runnable doubleTapTimeoutCallback) {
Winson Chungfa7053782016-11-08 15:45:10 -080068 mViewConfig = viewConfig;
Winson Chungbca03112017-08-16 10:38:15 -070069 mHandler = handler;
70 mDoubleTapTimeoutCallback = doubleTapTimeoutCallback;
Winson Chungfa7053782016-11-08 15:45:10 -080071 }
72
73 /**
Winson Chung85d39982017-02-24 15:21:25 -080074 * Resets this state.
75 */
76 public void reset() {
77 mAllowDraggingOffscreen = false;
78 mIsDragging = false;
79 mStartedDragging = false;
80 mIsUserInteracting = false;
81 }
82
83 /**
Winson Chung8ec0e162017-07-07 14:49:49 -070084 * Processes a given touch event and updates the state.
Winson Chungfa7053782016-11-08 15:45:10 -080085 */
86 public void onTouchEvent(MotionEvent ev) {
Vishnu Nair18999552018-12-13 09:28:11 -080087 switch (ev.getActionMasked()) {
Winson Chungfa7053782016-11-08 15:45:10 -080088 case MotionEvent.ACTION_DOWN: {
Winson Chung85d39982017-02-24 15:21:25 -080089 if (!mAllowTouches) {
90 return;
91 }
92
Winson Chungfa7053782016-11-08 15:45:10 -080093 // Initialize the velocity tracker
94 initOrResetVelocityTracker();
Vishnu Nair18999552018-12-13 09:28:11 -080095 addMovement(ev);
Winson Chung85d39982017-02-24 15:21:25 -080096
Winson Chungfa7053782016-11-08 15:45:10 -080097 mActivePointerId = ev.getPointerId(0);
Winson Chung040e7222017-03-21 10:47:09 -070098 if (DEBUG) {
99 Log.e(TAG, "Setting active pointer id on DOWN: " + mActivePointerId);
100 }
Vishnu Nair18999552018-12-13 09:28:11 -0800101 mLastTouch.set(ev.getRawX(), ev.getRawY());
Winson Chungfa7053782016-11-08 15:45:10 -0800102 mDownTouch.set(mLastTouch);
Winson Chungd5a01592016-11-11 16:25:04 -0800103 mAllowDraggingOffscreen = true;
Winson Chung2a82fe52017-02-02 14:43:34 -0800104 mIsUserInteracting = true;
Winson Chungbca03112017-08-16 10:38:15 -0700105 mDownTouchTime = ev.getEventTime();
106 mIsDoubleTap = !mPreviouslyDragging &&
107 (mDownTouchTime - mLastDownTouchTime) < DOUBLE_TAP_TIMEOUT;
108 mIsWaitingForDoubleTap = false;
Arthur Hung0ede6e92019-08-30 16:06:59 +0800109 mIsDragging = false;
Winson Chungbca03112017-08-16 10:38:15 -0700110 mLastDownTouchTime = mDownTouchTime;
111 if (mDoubleTapTimeoutCallback != null) {
112 mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
113 }
Winson Chungfa7053782016-11-08 15:45:10 -0800114 break;
115 }
116 case MotionEvent.ACTION_MOVE: {
Winson Chung85d39982017-02-24 15:21:25 -0800117 // Skip event if we did not start processing this touch gesture
118 if (!mIsUserInteracting) {
119 break;
120 }
121
Winson Chungfa7053782016-11-08 15:45:10 -0800122 // Update the velocity tracker
Vishnu Nair18999552018-12-13 09:28:11 -0800123 addMovement(ev);
Winson Chungfa7053782016-11-08 15:45:10 -0800124 int pointerIndex = ev.findPointerIndex(mActivePointerId);
Winson Chung040e7222017-03-21 10:47:09 -0700125 if (pointerIndex == -1) {
126 Log.e(TAG, "Invalid active pointer id on MOVE: " + mActivePointerId);
127 break;
128 }
129
Vishnu Nair18999552018-12-13 09:28:11 -0800130 float x = ev.getRawX(pointerIndex);
131 float y = ev.getRawY(pointerIndex);
Winson Chungfa7053782016-11-08 15:45:10 -0800132 mLastDelta.set(x - mLastTouch.x, y - mLastTouch.y);
133 mDownDelta.set(x - mDownTouch.x, y - mDownTouch.y);
134
135 boolean hasMovedBeyondTap = mDownDelta.length() > mViewConfig.getScaledTouchSlop();
136 if (!mIsDragging) {
137 if (hasMovedBeyondTap) {
138 mIsDragging = true;
139 mStartedDragging = true;
140 }
141 } else {
142 mStartedDragging = false;
143 }
144 mLastTouch.set(x, y);
145 break;
146 }
147 case MotionEvent.ACTION_POINTER_UP: {
Winson Chung85d39982017-02-24 15:21:25 -0800148 // Skip event if we did not start processing this touch gesture
149 if (!mIsUserInteracting) {
150 break;
151 }
152
Winson Chungfa7053782016-11-08 15:45:10 -0800153 // Update the velocity tracker
Vishnu Nair18999552018-12-13 09:28:11 -0800154 addMovement(ev);
Winson Chungfa7053782016-11-08 15:45:10 -0800155
156 int pointerIndex = ev.getActionIndex();
157 int pointerId = ev.getPointerId(pointerIndex);
158 if (pointerId == mActivePointerId) {
159 // Select a new active pointer id and reset the movement state
160 final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
161 mActivePointerId = ev.getPointerId(newPointerIndex);
Winson Chung040e7222017-03-21 10:47:09 -0700162 if (DEBUG) {
163 Log.e(TAG, "Relinquish active pointer id on POINTER_UP: " +
164 mActivePointerId);
165 }
Vishnu Nair18999552018-12-13 09:28:11 -0800166 mLastTouch.set(ev.getRawX(newPointerIndex), ev.getRawY(newPointerIndex));
Winson Chungfa7053782016-11-08 15:45:10 -0800167 }
168 break;
169 }
170 case MotionEvent.ACTION_UP: {
Winson Chung85d39982017-02-24 15:21:25 -0800171 // Skip event if we did not start processing this touch gesture
172 if (!mIsUserInteracting) {
173 break;
174 }
175
Winson Chungfa7053782016-11-08 15:45:10 -0800176 // Update the velocity tracker
Vishnu Nair18999552018-12-13 09:28:11 -0800177 addMovement(ev);
Winson Chungfa7053782016-11-08 15:45:10 -0800178 mVelocityTracker.computeCurrentVelocity(1000,
179 mViewConfig.getScaledMaximumFlingVelocity());
180 mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
181
182 int pointerIndex = ev.findPointerIndex(mActivePointerId);
Winson Chung040e7222017-03-21 10:47:09 -0700183 if (pointerIndex == -1) {
184 Log.e(TAG, "Invalid active pointer id on UP: " + mActivePointerId);
185 break;
186 }
187
Winson Chungbca03112017-08-16 10:38:15 -0700188 mUpTouchTime = ev.getEventTime();
Vishnu Nair18999552018-12-13 09:28:11 -0800189 mLastTouch.set(ev.getRawX(pointerIndex), ev.getRawY(pointerIndex));
Winson Chungbca03112017-08-16 10:38:15 -0700190 mPreviouslyDragging = mIsDragging;
191 mIsWaitingForDoubleTap = !mIsDoubleTap && !mIsDragging &&
192 (mUpTouchTime - mDownTouchTime) < DOUBLE_TAP_TIMEOUT;
Winson Chungfa7053782016-11-08 15:45:10 -0800193
194 // Fall through to clean up
195 }
196 case MotionEvent.ACTION_CANCEL: {
197 recycleVelocityTracker();
198 break;
199 }
200 }
201 }
202
203 /**
204 * @return the velocity of the active touch pointer at the point it is lifted off the screen.
205 */
206 public PointF getVelocity() {
207 return mVelocity;
208 }
209
210 /**
211 * @return the last touch position of the active pointer.
212 */
213 public PointF getLastTouchPosition() {
214 return mLastTouch;
215 }
216
217 /**
218 * @return the movement delta between the last handled touch event and the previous touch
219 * position.
220 */
221 public PointF getLastTouchDelta() {
222 return mLastDelta;
223 }
224
225 /**
Mady Mellor57d22552017-03-09 15:37:13 -0800226 * @return the down touch position.
227 */
228 public PointF getDownTouchPosition() {
229 return mDownTouch;
230 }
231
232 /**
Winson Chungfa7053782016-11-08 15:45:10 -0800233 * @return the movement delta between the last handled touch event and the down touch
234 * position.
235 */
236 public PointF getDownTouchDelta() {
237 return mDownDelta;
238 }
239
240 /**
241 * @return whether the user has started dragging.
242 */
243 public boolean isDragging() {
244 return mIsDragging;
245 }
246
247 /**
Winson Chung2a82fe52017-02-02 14:43:34 -0800248 * @return whether the user is currently interacting with the PiP.
249 */
250 public boolean isUserInteracting() {
251 return mIsUserInteracting;
252 }
253
254 /**
Winson Chungfa7053782016-11-08 15:45:10 -0800255 * @return whether the user has started dragging just in the last handled touch event.
256 */
257 public boolean startedDragging() {
258 return mStartedDragging;
259 }
260
Winson Chungd5a01592016-11-11 16:25:04 -0800261 /**
Winson Chung85d39982017-02-24 15:21:25 -0800262 * Sets whether touching is currently allowed.
263 */
264 public void setAllowTouches(boolean allowTouches) {
265 mAllowTouches = allowTouches;
266
267 // If the user happens to touch down before this is sent from the system during a transition
268 // then block any additional handling by resetting the state now
269 if (mIsUserInteracting) {
270 reset();
271 }
272 }
273
274 /**
Winson Chungd5a01592016-11-11 16:25:04 -0800275 * Disallows dragging offscreen for the duration of the current gesture.
276 */
277 public void setDisallowDraggingOffscreen() {
278 mAllowDraggingOffscreen = false;
279 }
280
281 /**
282 * @return whether dragging offscreen is allowed during this gesture.
283 */
284 public boolean allowDraggingOffscreen() {
285 return mAllowDraggingOffscreen;
286 }
287
Winson Chungbca03112017-08-16 10:38:15 -0700288 /**
289 * @return whether this gesture is a double-tap.
290 */
291 public boolean isDoubleTap() {
292 return mIsDoubleTap;
293 }
294
295 /**
296 * @return whether this gesture will potentially lead to a following double-tap.
297 */
298 public boolean isWaitingForDoubleTap() {
299 return mIsWaitingForDoubleTap;
300 }
301
302 /**
303 * Schedules the callback to run if the next double tap does not occur. Only runs if
304 * isWaitingForDoubleTap() is true.
305 */
306 public void scheduleDoubleTapTimeoutCallback() {
307 if (mIsWaitingForDoubleTap) {
308 long delay = getDoubleTapTimeoutCallbackDelay();
309 mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
310 mHandler.postDelayed(mDoubleTapTimeoutCallback, delay);
311 }
312 }
313
314 @VisibleForTesting long getDoubleTapTimeoutCallbackDelay() {
315 if (mIsWaitingForDoubleTap) {
316 return Math.max(0, DOUBLE_TAP_TIMEOUT - (mUpTouchTime - mDownTouchTime));
317 }
318 return -1;
319 }
320
Winson Chungfa7053782016-11-08 15:45:10 -0800321 private void initOrResetVelocityTracker() {
322 if (mVelocityTracker == null) {
323 mVelocityTracker = VelocityTracker.obtain();
324 } else {
325 mVelocityTracker.clear();
326 }
327 }
328
329 private void recycleVelocityTracker() {
330 if (mVelocityTracker != null) {
331 mVelocityTracker.recycle();
332 mVelocityTracker = null;
333 }
334 }
Winson Chung29a78652017-02-09 18:35:26 -0800335
Vishnu Nair18999552018-12-13 09:28:11 -0800336 private void addMovement(MotionEvent event) {
337 // Add movement to velocity tracker using raw screen X and Y coordinates instead
338 // of window coordinates because the window frame may be moving at the same time.
339 float deltaX = event.getRawX() - event.getX();
340 float deltaY = event.getRawY() - event.getY();
341 event.offsetLocation(deltaX, deltaY);
342 mVelocityTracker.addMovement(event);
343 event.offsetLocation(-deltaX, -deltaY);
344 }
345
Winson Chung29a78652017-02-09 18:35:26 -0800346 public void dump(PrintWriter pw, String prefix) {
347 final String innerPrefix = prefix + " ";
348 pw.println(prefix + TAG);
Winson Chung85d39982017-02-24 15:21:25 -0800349 pw.println(innerPrefix + "mAllowTouches=" + mAllowTouches);
Winson Chung040e7222017-03-21 10:47:09 -0700350 pw.println(innerPrefix + "mActivePointerId=" + mActivePointerId);
Winson Chung29a78652017-02-09 18:35:26 -0800351 pw.println(innerPrefix + "mDownTouch=" + mDownTouch);
352 pw.println(innerPrefix + "mDownDelta=" + mDownDelta);
353 pw.println(innerPrefix + "mLastTouch=" + mLastTouch);
354 pw.println(innerPrefix + "mLastDelta=" + mLastDelta);
355 pw.println(innerPrefix + "mVelocity=" + mVelocity);
356 pw.println(innerPrefix + "mIsUserInteracting=" + mIsUserInteracting);
357 pw.println(innerPrefix + "mIsDragging=" + mIsDragging);
358 pw.println(innerPrefix + "mStartedDragging=" + mStartedDragging);
359 pw.println(innerPrefix + "mAllowDraggingOffscreen=" + mAllowDraggingOffscreen);
360 }
Winson Chungfa7053782016-11-08 15:45:10 -0800361}