blob: 8f9b4c211a1ebdfe33fe50facafa4eadf55fd670 [file] [log] [blame]
Winson Chungcdbbb7e2014-06-24 12:11:49 -07001/*
2 * Copyright (C) 2014 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.recents.views;
18
19import android.content.Context;
20import android.view.MotionEvent;
21import android.view.VelocityTracker;
22import android.view.View;
23import android.view.ViewConfiguration;
24import android.view.ViewParent;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070025import com.android.systemui.recents.Constants;
Winson Chungebfc6982014-08-26 12:25:34 -070026import com.android.systemui.recents.RecentsConfiguration;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070027
28/* Handles touch events for a TaskStackView. */
29class TaskStackViewTouchHandler implements SwipeHelper.Callback {
30 static int INACTIVE_POINTER_ID = -1;
31
Winson Chungebfc6982014-08-26 12:25:34 -070032 RecentsConfiguration mConfig;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070033 TaskStackView mSv;
Winson Chung012ef362014-07-31 18:36:25 -070034 TaskStackViewScroller mScroller;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070035 VelocityTracker mVelocityTracker;
36
37 boolean mIsScrolling;
38
Winson Chung012ef362014-07-31 18:36:25 -070039 float mInitialP;
40 float mLastP;
41 float mTotalPMotion;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070042 int mInitialMotionX, mInitialMotionY;
43 int mLastMotionX, mLastMotionY;
44 int mActivePointerId = INACTIVE_POINTER_ID;
45 TaskView mActiveTaskView = null;
46
Winson Chungcdbbb7e2014-06-24 12:11:49 -070047 int mMinimumVelocity;
48 int mMaximumVelocity;
49 // The scroll touch slop is used to calculate when we start scrolling
50 int mScrollTouchSlop;
51 // The page touch slop is used to calculate when we start swiping
52 float mPagingTouchSlop;
53
54 SwipeHelper mSwipeHelper;
55 boolean mInterceptedBySwipeHelper;
56
Winson Chungebfc6982014-08-26 12:25:34 -070057 public TaskStackViewTouchHandler(Context context, TaskStackView sv,
58 RecentsConfiguration config, TaskStackViewScroller scroller) {
Winson Chungcdbbb7e2014-06-24 12:11:49 -070059 ViewConfiguration configuration = ViewConfiguration.get(context);
60 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
61 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
62 mScrollTouchSlop = configuration.getScaledTouchSlop();
63 mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
64 mSv = sv;
Winson Chung012ef362014-07-31 18:36:25 -070065 mScroller = scroller;
Winson Chungebfc6982014-08-26 12:25:34 -070066 mConfig = config;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070067
68 float densityScale = context.getResources().getDisplayMetrics().density;
69 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop);
70 mSwipeHelper.setMinAlpha(1f);
71 }
72
73 /** Velocity tracker helpers */
74 void initOrResetVelocityTracker() {
75 if (mVelocityTracker == null) {
76 mVelocityTracker = VelocityTracker.obtain();
77 } else {
78 mVelocityTracker.clear();
79 }
80 }
81 void initVelocityTrackerIfNotExists() {
82 if (mVelocityTracker == null) {
83 mVelocityTracker = VelocityTracker.obtain();
84 }
85 }
86 void recycleVelocityTracker() {
87 if (mVelocityTracker != null) {
88 mVelocityTracker.recycle();
89 mVelocityTracker = null;
90 }
91 }
92
93 /** Returns the view at the specified coordinates */
94 TaskView findViewAtPoint(int x, int y) {
95 int childCount = mSv.getChildCount();
96 for (int i = childCount - 1; i >= 0; i--) {
97 TaskView tv = (TaskView) mSv.getChildAt(i);
98 if (tv.getVisibility() == View.VISIBLE) {
99 if (mSv.isTransformedTouchPointInView(x, y, tv)) {
100 return tv;
101 }
102 }
103 }
104 return null;
105 }
106
Winson Chung012ef362014-07-31 18:36:25 -0700107 /** Constructs a simulated motion event for the current stack scroll. */
108 MotionEvent createMotionEventForStackScroll(MotionEvent ev) {
109 MotionEvent pev = MotionEvent.obtainNoHistory(ev);
110 pev.setLocation(0, mScroller.progressToScrollRange(mScroller.getStackScroll()));
111 return pev;
112 }
113
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700114 /** Touch preprocessing for handling below */
115 public boolean onInterceptTouchEvent(MotionEvent ev) {
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700116 // Return early if we have no children
117 boolean hasChildren = (mSv.getChildCount() > 0);
118 if (!hasChildren) {
119 return false;
120 }
121
122 // Pass through to swipe helper if we are swiping
123 mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
124 if (mInterceptedBySwipeHelper) {
125 return true;
126 }
127
Winson Chung012ef362014-07-31 18:36:25 -0700128 boolean wasScrolling = mScroller.isScrolling() ||
129 (mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning());
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700130 int action = ev.getAction();
131 switch (action & MotionEvent.ACTION_MASK) {
132 case MotionEvent.ACTION_DOWN: {
133 // Save the touch down info
134 mInitialMotionX = mLastMotionX = (int) ev.getX();
135 mInitialMotionY = mLastMotionY = (int) ev.getY();
Winson Chung012ef362014-07-31 18:36:25 -0700136 mInitialP = mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700137 mActivePointerId = ev.getPointerId(0);
138 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
139 // Stop the current scroll if it is still flinging
Winson Chung012ef362014-07-31 18:36:25 -0700140 mScroller.stopScroller();
141 mScroller.stopBoundScrollAnimation();
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700142 // Initialize the velocity tracker
143 initOrResetVelocityTracker();
Winson Chung012ef362014-07-31 18:36:25 -0700144 mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700145 // Check if the scroller is finished yet
Winson Chung012ef362014-07-31 18:36:25 -0700146 mIsScrolling = mScroller.isScrolling();
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700147 break;
148 }
149 case MotionEvent.ACTION_MOVE: {
150 if (mActivePointerId == INACTIVE_POINTER_ID) break;
151
152 int activePointerIndex = ev.findPointerIndex(mActivePointerId);
153 int y = (int) ev.getY(activePointerIndex);
154 int x = (int) ev.getX(activePointerIndex);
155 if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
156 // Save the touch move info
157 mIsScrolling = true;
158 // Initialize the velocity tracker if necessary
159 initVelocityTrackerIfNotExists();
Winson Chung012ef362014-07-31 18:36:25 -0700160 mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700161 // Disallow parents from intercepting touch events
162 final ViewParent parent = mSv.getParent();
163 if (parent != null) {
164 parent.requestDisallowInterceptTouchEvent(true);
165 }
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700166 }
167
168 mLastMotionX = x;
169 mLastMotionY = y;
Winson Chung012ef362014-07-31 18:36:25 -0700170 mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700171 break;
172 }
173 case MotionEvent.ACTION_CANCEL:
174 case MotionEvent.ACTION_UP: {
175 // Animate the scroll back if we've cancelled
Winson Chung012ef362014-07-31 18:36:25 -0700176 mScroller.animateBoundScroll();
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700177 // Reset the drag state and the velocity tracker
178 mIsScrolling = false;
179 mActivePointerId = INACTIVE_POINTER_ID;
180 mActiveTaskView = null;
Winson Chung012ef362014-07-31 18:36:25 -0700181 mTotalPMotion = 0;
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700182 recycleVelocityTracker();
183 break;
184 }
185 }
186
187 return wasScrolling || mIsScrolling;
188 }
189
190 /** Handles touch events once we have intercepted them */
191 public boolean onTouchEvent(MotionEvent ev) {
Winson Chungb99b18e2014-08-04 15:21:46 -0700192
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700193 // Short circuit if we have no children
194 boolean hasChildren = (mSv.getChildCount() > 0);
195 if (!hasChildren) {
196 return false;
197 }
198
199 // Pass through to swipe helper if we are swiping
200 if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
201 return true;
202 }
203
204 // Update the velocity tracker
205 initVelocityTrackerIfNotExists();
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700206
207 int action = ev.getAction();
208 switch (action & MotionEvent.ACTION_MASK) {
209 case MotionEvent.ACTION_DOWN: {
210 // Save the touch down info
211 mInitialMotionX = mLastMotionX = (int) ev.getX();
212 mInitialMotionY = mLastMotionY = (int) ev.getY();
Winson Chung012ef362014-07-31 18:36:25 -0700213 mInitialP = mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700214 mActivePointerId = ev.getPointerId(0);
215 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
216 // Stop the current scroll if it is still flinging
Winson Chung012ef362014-07-31 18:36:25 -0700217 mScroller.stopScroller();
218 mScroller.stopBoundScrollAnimation();
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700219 // Initialize the velocity tracker
220 initOrResetVelocityTracker();
Winson Chung012ef362014-07-31 18:36:25 -0700221 mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700222 // Disallow parents from intercepting touch events
223 final ViewParent parent = mSv.getParent();
224 if (parent != null) {
225 parent.requestDisallowInterceptTouchEvent(true);
226 }
227 break;
228 }
229 case MotionEvent.ACTION_POINTER_DOWN: {
230 final int index = ev.getActionIndex();
231 mActivePointerId = ev.getPointerId(index);
232 mLastMotionX = (int) ev.getX(index);
233 mLastMotionY = (int) ev.getY(index);
Winson Chung012ef362014-07-31 18:36:25 -0700234 mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700235 break;
236 }
237 case MotionEvent.ACTION_MOVE: {
238 if (mActivePointerId == INACTIVE_POINTER_ID) break;
239
240 int activePointerIndex = ev.findPointerIndex(mActivePointerId);
241 int x = (int) ev.getX(activePointerIndex);
242 int y = (int) ev.getY(activePointerIndex);
243 int yTotal = Math.abs(y - mInitialMotionY);
Winson Chung012ef362014-07-31 18:36:25 -0700244 float curP = mSv.mLayoutAlgorithm.screenYToCurveProgress(y);
245 float deltaP = mLastP - curP;
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700246 if (!mIsScrolling) {
247 if (yTotal > mScrollTouchSlop) {
248 mIsScrolling = true;
249 // Initialize the velocity tracker
250 initOrResetVelocityTracker();
Winson Chung012ef362014-07-31 18:36:25 -0700251 mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700252 // Disallow parents from intercepting touch events
253 final ViewParent parent = mSv.getParent();
254 if (parent != null) {
255 parent.requestDisallowInterceptTouchEvent(true);
256 }
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700257 }
258 }
259 if (mIsScrolling) {
Winson Chung012ef362014-07-31 18:36:25 -0700260 float curStackScroll = mScroller.getStackScroll();
261 float overScrollAmount = mScroller.getScrollAmountOutOfBounds(curStackScroll + deltaP);
262 if (Float.compare(overScrollAmount, 0f) != 0) {
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700263 // Bound the overscroll to a fixed amount, and inversely scale the y-movement
264 // relative to how close we are to the max overscroll
Winson Chungebfc6982014-08-26 12:25:34 -0700265 float maxOverScroll = mConfig.taskStackOverscrollPct;
Winson Chung012ef362014-07-31 18:36:25 -0700266 deltaP *= (1f - (Math.min(maxOverScroll, overScrollAmount)
267 / maxOverScroll));
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700268 }
Winson Chung012ef362014-07-31 18:36:25 -0700269 mScroller.setStackScroll(curStackScroll + deltaP);
270 if (mScroller.isScrollOutOfBounds()) {
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700271 mVelocityTracker.clear();
Winson Chung012ef362014-07-31 18:36:25 -0700272 } else {
273 mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700274 }
275 }
276 mLastMotionX = x;
277 mLastMotionY = y;
Winson Chung012ef362014-07-31 18:36:25 -0700278 mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
279 mTotalPMotion += Math.abs(deltaP);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700280 break;
281 }
282 case MotionEvent.ACTION_UP: {
283 final VelocityTracker velocityTracker = mVelocityTracker;
284 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
285 int velocity = (int) velocityTracker.getYVelocity(mActivePointerId);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700286 if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700287 int overscrollRange = (int) (Math.min(1f,
288 Math.abs((float) velocity / mMaximumVelocity)) *
289 Constants.Values.TaskStackView.TaskStackOverscrollRange);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700290 // Fling scroll
Winson Chung012ef362014-07-31 18:36:25 -0700291 mScroller.mScroller.fling(0, mScroller.progressToScrollRange(mScroller.getStackScroll()),
292 0, velocity,
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700293 0, 0,
Winson Chung012ef362014-07-31 18:36:25 -0700294 mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMinScrollP),
295 mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMaxScrollP),
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700296 0, overscrollRange);
297 // Invalidate to kick off computeScroll
Winson Chung012ef362014-07-31 18:36:25 -0700298 mSv.invalidate();
299 } else if (mScroller.isScrollOutOfBounds()) {
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700300 // Animate the scroll back into bounds
Winson Chung012ef362014-07-31 18:36:25 -0700301 mScroller.animateBoundScroll();
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700302 }
303
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700304 mActivePointerId = INACTIVE_POINTER_ID;
305 mIsScrolling = false;
Winson Chung012ef362014-07-31 18:36:25 -0700306 mTotalPMotion = 0;
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700307 recycleVelocityTracker();
308 break;
309 }
310 case MotionEvent.ACTION_POINTER_UP: {
311 int pointerIndex = ev.getActionIndex();
312 int pointerId = ev.getPointerId(pointerIndex);
313 if (pointerId == mActivePointerId) {
314 // Select a new active pointer id and reset the motion state
315 final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
316 mActivePointerId = ev.getPointerId(newPointerIndex);
317 mLastMotionX = (int) ev.getX(newPointerIndex);
318 mLastMotionY = (int) ev.getY(newPointerIndex);
Winson Chung012ef362014-07-31 18:36:25 -0700319 mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700320 mVelocityTracker.clear();
321 }
322 break;
323 }
324 case MotionEvent.ACTION_CANCEL: {
Winson Chung012ef362014-07-31 18:36:25 -0700325 if (mScroller.isScrollOutOfBounds()) {
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700326 // Animate the scroll back into bounds
Winson Chung012ef362014-07-31 18:36:25 -0700327 mScroller.animateBoundScroll();
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700328 }
329 mActivePointerId = INACTIVE_POINTER_ID;
330 mIsScrolling = false;
Winson Chung012ef362014-07-31 18:36:25 -0700331 mTotalPMotion = 0;
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700332 recycleVelocityTracker();
333 break;
334 }
335 }
336 return true;
337 }
338
339 /**** SwipeHelper Implementation ****/
340
341 @Override
342 public View getChildAtPosition(MotionEvent ev) {
343 return findViewAtPoint((int) ev.getX(), (int) ev.getY());
344 }
345
346 @Override
347 public boolean canChildBeDismissed(View v) {
348 return true;
349 }
350
351 @Override
352 public void onBeginDrag(View v) {
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700353 TaskView tv = (TaskView) v;
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700354 // Disable clipping with the stack while we are swiping
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700355 tv.setClipViewInStack(false);
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700356 // Disallow touch events from this task view
Winson Chung1f24c7e2014-07-11 17:06:48 -0700357 tv.setTouchEnabled(false);
358 // Hide the footer
Jason Monk56e09b42014-07-18 10:29:14 -0400359 tv.animateFooterVisibility(false, mSv.mConfig.taskViewLockToAppShortAnimDuration);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700360 // Disallow parents from intercepting touch events
361 final ViewParent parent = mSv.getParent();
362 if (parent != null) {
363 parent.requestDisallowInterceptTouchEvent(true);
364 }
365 }
366
367 @Override
368 public void onSwipeChanged(View v, float delta) {
369 // Do nothing
370 }
371
372 @Override
373 public void onChildDismissed(View v) {
374 TaskView tv = (TaskView) v;
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700375 // Re-enable clipping with the stack (we will reuse this view)
376 tv.setClipViewInStack(true);
Winson Chung3e3365b2014-07-17 18:39:02 -0700377 // Re-enable touch events from this task view
378 tv.setTouchEnabled(true);
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700379 // Remove the task view from the stack
Winson Chung7aceb9a2014-07-03 13:38:01 -0700380 mSv.onTaskViewDismissed(tv);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700381 }
382
383 @Override
384 public void onSnapBackCompleted(View v) {
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700385 TaskView tv = (TaskView) v;
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700386 // Re-enable clipping with the stack
387 tv.setClipViewInStack(true);
388 // Re-enable touch events from this task view
Winson Chung1f24c7e2014-07-11 17:06:48 -0700389 tv.setTouchEnabled(true);
390 // Restore the footer
Jason Monk56e09b42014-07-18 10:29:14 -0400391 tv.animateFooterVisibility(true, mSv.mConfig.taskViewLockToAppShortAnimDuration);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700392 }
Winson Chung7aceb9a2014-07-03 13:38:01 -0700393
394 @Override
395 public void onDragCancelled(View v) {
396 // Do nothing
397 }
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700398}