blob: 69efbc8575e06e5d06cd72baf263d6696da4201d [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;
109 mLastDownTouchTime = mDownTouchTime;
110 if (mDoubleTapTimeoutCallback != null) {
111 mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
112 }
Winson Chungfa7053782016-11-08 15:45:10 -0800113 break;
114 }
115 case MotionEvent.ACTION_MOVE: {
Winson Chung85d39982017-02-24 15:21:25 -0800116 // Skip event if we did not start processing this touch gesture
117 if (!mIsUserInteracting) {
118 break;
119 }
120
Winson Chungfa7053782016-11-08 15:45:10 -0800121 // Update the velocity tracker
Vishnu Nair18999552018-12-13 09:28:11 -0800122 addMovement(ev);
Winson Chungfa7053782016-11-08 15:45:10 -0800123 int pointerIndex = ev.findPointerIndex(mActivePointerId);
Winson Chung040e7222017-03-21 10:47:09 -0700124 if (pointerIndex == -1) {
125 Log.e(TAG, "Invalid active pointer id on MOVE: " + mActivePointerId);
126 break;
127 }
128
Vishnu Nair18999552018-12-13 09:28:11 -0800129 float x = ev.getRawX(pointerIndex);
130 float y = ev.getRawY(pointerIndex);
Winson Chungfa7053782016-11-08 15:45:10 -0800131 mLastDelta.set(x - mLastTouch.x, y - mLastTouch.y);
132 mDownDelta.set(x - mDownTouch.x, y - mDownTouch.y);
133
134 boolean hasMovedBeyondTap = mDownDelta.length() > mViewConfig.getScaledTouchSlop();
135 if (!mIsDragging) {
136 if (hasMovedBeyondTap) {
137 mIsDragging = true;
138 mStartedDragging = true;
139 }
140 } else {
141 mStartedDragging = false;
142 }
143 mLastTouch.set(x, y);
144 break;
145 }
146 case MotionEvent.ACTION_POINTER_UP: {
Winson Chung85d39982017-02-24 15:21:25 -0800147 // Skip event if we did not start processing this touch gesture
148 if (!mIsUserInteracting) {
149 break;
150 }
151
Winson Chungfa7053782016-11-08 15:45:10 -0800152 // Update the velocity tracker
Vishnu Nair18999552018-12-13 09:28:11 -0800153 addMovement(ev);
Winson Chungfa7053782016-11-08 15:45:10 -0800154
155 int pointerIndex = ev.getActionIndex();
156 int pointerId = ev.getPointerId(pointerIndex);
157 if (pointerId == mActivePointerId) {
158 // Select a new active pointer id and reset the movement state
159 final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
160 mActivePointerId = ev.getPointerId(newPointerIndex);
Winson Chung040e7222017-03-21 10:47:09 -0700161 if (DEBUG) {
162 Log.e(TAG, "Relinquish active pointer id on POINTER_UP: " +
163 mActivePointerId);
164 }
Vishnu Nair18999552018-12-13 09:28:11 -0800165 mLastTouch.set(ev.getRawX(newPointerIndex), ev.getRawY(newPointerIndex));
Winson Chungfa7053782016-11-08 15:45:10 -0800166 }
167 break;
168 }
169 case MotionEvent.ACTION_UP: {
Winson Chung85d39982017-02-24 15:21:25 -0800170 // Skip event if we did not start processing this touch gesture
171 if (!mIsUserInteracting) {
172 break;
173 }
174
Winson Chungfa7053782016-11-08 15:45:10 -0800175 // Update the velocity tracker
Vishnu Nair18999552018-12-13 09:28:11 -0800176 addMovement(ev);
Winson Chungfa7053782016-11-08 15:45:10 -0800177 mVelocityTracker.computeCurrentVelocity(1000,
178 mViewConfig.getScaledMaximumFlingVelocity());
179 mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
180
181 int pointerIndex = ev.findPointerIndex(mActivePointerId);
Winson Chung040e7222017-03-21 10:47:09 -0700182 if (pointerIndex == -1) {
183 Log.e(TAG, "Invalid active pointer id on UP: " + mActivePointerId);
184 break;
185 }
186
Winson Chungbca03112017-08-16 10:38:15 -0700187 mUpTouchTime = ev.getEventTime();
Vishnu Nair18999552018-12-13 09:28:11 -0800188 mLastTouch.set(ev.getRawX(pointerIndex), ev.getRawY(pointerIndex));
Winson Chungbca03112017-08-16 10:38:15 -0700189 mPreviouslyDragging = mIsDragging;
190 mIsWaitingForDoubleTap = !mIsDoubleTap && !mIsDragging &&
191 (mUpTouchTime - mDownTouchTime) < DOUBLE_TAP_TIMEOUT;
Winson Chungfa7053782016-11-08 15:45:10 -0800192
193 // Fall through to clean up
194 }
195 case MotionEvent.ACTION_CANCEL: {
196 recycleVelocityTracker();
197 break;
198 }
199 }
200 }
201
202 /**
203 * @return the velocity of the active touch pointer at the point it is lifted off the screen.
204 */
205 public PointF getVelocity() {
206 return mVelocity;
207 }
208
209 /**
210 * @return the last touch position of the active pointer.
211 */
212 public PointF getLastTouchPosition() {
213 return mLastTouch;
214 }
215
216 /**
217 * @return the movement delta between the last handled touch event and the previous touch
218 * position.
219 */
220 public PointF getLastTouchDelta() {
221 return mLastDelta;
222 }
223
224 /**
Mady Mellor57d22552017-03-09 15:37:13 -0800225 * @return the down touch position.
226 */
227 public PointF getDownTouchPosition() {
228 return mDownTouch;
229 }
230
231 /**
Winson Chungfa7053782016-11-08 15:45:10 -0800232 * @return the movement delta between the last handled touch event and the down touch
233 * position.
234 */
235 public PointF getDownTouchDelta() {
236 return mDownDelta;
237 }
238
239 /**
240 * @return whether the user has started dragging.
241 */
242 public boolean isDragging() {
243 return mIsDragging;
244 }
245
246 /**
Winson Chung2a82fe52017-02-02 14:43:34 -0800247 * @return whether the user is currently interacting with the PiP.
248 */
249 public boolean isUserInteracting() {
250 return mIsUserInteracting;
251 }
252
253 /**
Winson Chungfa7053782016-11-08 15:45:10 -0800254 * @return whether the user has started dragging just in the last handled touch event.
255 */
256 public boolean startedDragging() {
257 return mStartedDragging;
258 }
259
Winson Chungd5a01592016-11-11 16:25:04 -0800260 /**
Winson Chung85d39982017-02-24 15:21:25 -0800261 * Sets whether touching is currently allowed.
262 */
263 public void setAllowTouches(boolean allowTouches) {
264 mAllowTouches = allowTouches;
265
266 // If the user happens to touch down before this is sent from the system during a transition
267 // then block any additional handling by resetting the state now
268 if (mIsUserInteracting) {
269 reset();
270 }
271 }
272
273 /**
Winson Chungd5a01592016-11-11 16:25:04 -0800274 * Disallows dragging offscreen for the duration of the current gesture.
275 */
276 public void setDisallowDraggingOffscreen() {
277 mAllowDraggingOffscreen = false;
278 }
279
280 /**
281 * @return whether dragging offscreen is allowed during this gesture.
282 */
283 public boolean allowDraggingOffscreen() {
284 return mAllowDraggingOffscreen;
285 }
286
Winson Chungbca03112017-08-16 10:38:15 -0700287 /**
288 * @return whether this gesture is a double-tap.
289 */
290 public boolean isDoubleTap() {
291 return mIsDoubleTap;
292 }
293
294 /**
295 * @return whether this gesture will potentially lead to a following double-tap.
296 */
297 public boolean isWaitingForDoubleTap() {
298 return mIsWaitingForDoubleTap;
299 }
300
301 /**
302 * Schedules the callback to run if the next double tap does not occur. Only runs if
303 * isWaitingForDoubleTap() is true.
304 */
305 public void scheduleDoubleTapTimeoutCallback() {
306 if (mIsWaitingForDoubleTap) {
307 long delay = getDoubleTapTimeoutCallbackDelay();
308 mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
309 mHandler.postDelayed(mDoubleTapTimeoutCallback, delay);
310 }
311 }
312
313 @VisibleForTesting long getDoubleTapTimeoutCallbackDelay() {
314 if (mIsWaitingForDoubleTap) {
315 return Math.max(0, DOUBLE_TAP_TIMEOUT - (mUpTouchTime - mDownTouchTime));
316 }
317 return -1;
318 }
319
Winson Chungfa7053782016-11-08 15:45:10 -0800320 private void initOrResetVelocityTracker() {
321 if (mVelocityTracker == null) {
322 mVelocityTracker = VelocityTracker.obtain();
323 } else {
324 mVelocityTracker.clear();
325 }
326 }
327
328 private void recycleVelocityTracker() {
329 if (mVelocityTracker != null) {
330 mVelocityTracker.recycle();
331 mVelocityTracker = null;
332 }
333 }
Winson Chung29a78652017-02-09 18:35:26 -0800334
Vishnu Nair18999552018-12-13 09:28:11 -0800335 private void addMovement(MotionEvent event) {
336 // Add movement to velocity tracker using raw screen X and Y coordinates instead
337 // of window coordinates because the window frame may be moving at the same time.
338 float deltaX = event.getRawX() - event.getX();
339 float deltaY = event.getRawY() - event.getY();
340 event.offsetLocation(deltaX, deltaY);
341 mVelocityTracker.addMovement(event);
342 event.offsetLocation(-deltaX, -deltaY);
343 }
344
Winson Chung29a78652017-02-09 18:35:26 -0800345 public void dump(PrintWriter pw, String prefix) {
346 final String innerPrefix = prefix + " ";
347 pw.println(prefix + TAG);
Winson Chung85d39982017-02-24 15:21:25 -0800348 pw.println(innerPrefix + "mAllowTouches=" + mAllowTouches);
Winson Chung040e7222017-03-21 10:47:09 -0700349 pw.println(innerPrefix + "mActivePointerId=" + mActivePointerId);
Winson Chung29a78652017-02-09 18:35:26 -0800350 pw.println(innerPrefix + "mDownTouch=" + mDownTouch);
351 pw.println(innerPrefix + "mDownDelta=" + mDownDelta);
352 pw.println(innerPrefix + "mLastTouch=" + mLastTouch);
353 pw.println(innerPrefix + "mLastDelta=" + mLastDelta);
354 pw.println(innerPrefix + "mVelocity=" + mVelocity);
355 pw.println(innerPrefix + "mIsUserInteracting=" + mIsUserInteracting);
356 pw.println(innerPrefix + "mIsDragging=" + mIsDragging);
357 pw.println(innerPrefix + "mStartedDragging=" + mStartedDragging);
358 pw.println(innerPrefix + "mAllowDraggingOffscreen=" + mAllowDraggingOffscreen);
359 }
Winson Chungfa7053782016-11-08 15:45:10 -0800360}