blob: e9f6f398a286b64c656347f83a85d9d3755f29cc [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
Winsonc5fd3502016-01-18 15:18:37 -080019import android.animation.Animator;
Winson36a5a2c2015-10-29 18:04:39 -070020import android.animation.ValueAnimator;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070021import android.content.Context;
Winson23afcae2015-10-28 11:14:54 -070022import android.content.res.Resources;
Winsona5e6b362015-11-02 17:17:20 -080023import android.graphics.Rect;
Winson8aa99592016-01-19 15:07:07 -080024import android.util.ArrayMap;
25import android.util.MutableBoolean;
Winson Chungd213a1e2014-10-02 11:18:30 -070026import android.view.InputDevice;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070027import android.view.MotionEvent;
28import android.view.VelocityTracker;
29import android.view.View;
30import android.view.ViewConfiguration;
31import android.view.ViewParent;
Winson8aa99592016-01-19 15:07:07 -080032import android.view.animation.Interpolator;
33import android.view.animation.PathInterpolator;
Winson Chung5c9f4b92015-06-25 16:16:46 -070034import com.android.internal.logging.MetricsLogger;
Winson2536c7e2015-10-01 15:49:31 -070035import com.android.systemui.R;
Winson671e8f92016-01-12 13:16:56 -080036import com.android.systemui.SwipeHelper;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070037import com.android.systemui.recents.Constants;
Winsona5e6b362015-11-02 17:17:20 -080038import com.android.systemui.recents.Recents;
Winson2536c7e2015-10-01 15:49:31 -070039import com.android.systemui.recents.events.EventBus;
Winson412e1802015-10-20 16:57:57 -070040import com.android.systemui.recents.events.activity.HideRecentsEvent;
Winson8b1871d2015-11-20 09:56:20 -080041import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
Winsondc8de842016-01-06 15:21:41 -080042import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
Winson8aa99592016-01-19 15:07:07 -080043import com.android.systemui.recents.misc.RectFEvaluator;
Winsona5e6b362015-11-02 17:17:20 -080044import com.android.systemui.recents.misc.SystemServicesProxy;
Winson36a5a2c2015-10-29 18:04:39 -070045import com.android.systemui.recents.misc.Utilities;
Winson8aa99592016-01-19 15:07:07 -080046import com.android.systemui.recents.model.Task;
Winson36a5a2c2015-10-29 18:04:39 -070047import com.android.systemui.statusbar.FlingAnimationUtils;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070048
Winson8aa99592016-01-19 15:07:07 -080049import java.util.ArrayList;
Winson Chung6ac8bd62015-01-07 16:38:35 -080050import java.util.List;
51
Winson8aa99592016-01-19 15:07:07 -080052/**
53 * Handles touch events for a TaskStackView.
54 */
Winson Chungcdbbb7e2014-06-24 12:11:49 -070055class TaskStackViewTouchHandler implements SwipeHelper.Callback {
Winson23afcae2015-10-28 11:14:54 -070056
Winson8aa99592016-01-19 15:07:07 -080057 private static final int INACTIVE_POINTER_ID = -1;
Winson23afcae2015-10-28 11:14:54 -070058
Winson8aa99592016-01-19 15:07:07 -080059 private static final RectFEvaluator RECT_EVALUATOR = new RectFEvaluator();
60 private static final Interpolator STACK_TRANSFORM_INTERPOLATOR =
61 new PathInterpolator(0.73f, 0.33f, 0.42f, 0.85f);
Winson Chungcdbbb7e2014-06-24 12:11:49 -070062
Winson35f30502015-09-28 11:24:36 -070063 Context mContext;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070064 TaskStackView mSv;
Winson Chung012ef362014-07-31 18:36:25 -070065 TaskStackViewScroller mScroller;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070066 VelocityTracker mVelocityTracker;
Winson36a5a2c2015-10-29 18:04:39 -070067 FlingAnimationUtils mFlingAnimUtils;
68 ValueAnimator mScrollFlingAnimator;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070069
70 boolean mIsScrolling;
Winson23afcae2015-10-28 11:14:54 -070071 float mDownScrollP;
72 int mDownX, mDownY;
Winson8b1871d2015-11-20 09:56:20 -080073 int mLastY;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070074 int mActivePointerId = INACTIVE_POINTER_ID;
Winson23afcae2015-10-28 11:14:54 -070075 int mOverscrollSize;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070076 TaskView mActiveTaskView = null;
77
Winson Chungcdbbb7e2014-06-24 12:11:49 -070078 int mMinimumVelocity;
79 int mMaximumVelocity;
80 // The scroll touch slop is used to calculate when we start scrolling
81 int mScrollTouchSlop;
James Cook4bd79b72015-03-17 07:33:28 -070082 // Used to calculate when a tap is outside a task view rectangle.
83 final int mWindowTouchSlop;
Winson Chungcdbbb7e2014-06-24 12:11:49 -070084
Winson55003902016-01-12 12:00:37 -080085 private final StackViewScrolledEvent mStackViewScrolledEvent = new StackViewScrolledEvent();
Winson8aa99592016-01-19 15:07:07 -080086
87 // The current and final set of task transforms, sized to match the list of tasks in the stack
88 private ArrayList<Task> mCurrentTasks = new ArrayList<>();
89 private ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
90 private ArrayList<TaskViewTransform> mFinalTaskTransforms = new ArrayList<>();
91 private ArrayMap<View, Animator> mSwipeHelperAnimations = new ArrayMap<>();
92 private TaskViewTransform mTmpTransform = new TaskViewTransform();
93 private float mTargetStackScroll;
94
Winson Chungcdbbb7e2014-06-24 12:11:49 -070095 SwipeHelper mSwipeHelper;
96 boolean mInterceptedBySwipeHelper;
97
Winson Chungebfc6982014-08-26 12:25:34 -070098 public TaskStackViewTouchHandler(Context context, TaskStackView sv,
Winson35f30502015-09-28 11:24:36 -070099 TaskStackViewScroller scroller) {
Winson23afcae2015-10-28 11:14:54 -0700100 Resources res = context.getResources();
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700101 ViewConfiguration configuration = ViewConfiguration.get(context);
Winson23afcae2015-10-28 11:14:54 -0700102 mContext = context;
Winson671e8f92016-01-12 13:16:56 -0800103 mSv = sv;
104 mScroller = scroller;
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700105 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
106 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
107 mScrollTouchSlop = configuration.getScaledTouchSlop();
James Cook4bd79b72015-03-17 07:33:28 -0700108 mWindowTouchSlop = configuration.getScaledWindowTouchSlop();
Winson36a5a2c2015-10-29 18:04:39 -0700109 mFlingAnimUtils = new FlingAnimationUtils(context, 0.2f);
Winson23afcae2015-10-28 11:14:54 -0700110 mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_stack_overscroll);
Winson671e8f92016-01-12 13:16:56 -0800111 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, context) {
112 @Override
113 protected float getSize(View v) {
114 return mSv.getWidth();
115 }
Winsonc5fd3502016-01-18 15:18:37 -0800116
117 @Override
Winson8aa99592016-01-19 15:07:07 -0800118 protected void prepareDismissAnimation(View v, Animator anim) {
119 mSwipeHelperAnimations.put(v, anim);
120 }
121
122 @Override
123 protected void prepareSnapBackAnimation(View v, Animator anim) {
Winsonc5fd3502016-01-18 15:18:37 -0800124 anim.setInterpolator(mSv.mFastOutSlowInInterpolator);
Winson8aa99592016-01-19 15:07:07 -0800125 mSwipeHelperAnimations.put(v, anim);
Winsonc5fd3502016-01-18 15:18:37 -0800126 }
Winson671e8f92016-01-12 13:16:56 -0800127 };
128 mSwipeHelper.setDisableHardwareLayers(true);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700129 }
130
131 /** Velocity tracker helpers */
132 void initOrResetVelocityTracker() {
133 if (mVelocityTracker == null) {
134 mVelocityTracker = VelocityTracker.obtain();
135 } else {
136 mVelocityTracker.clear();
137 }
138 }
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700139 void recycleVelocityTracker() {
140 if (mVelocityTracker != null) {
141 mVelocityTracker.recycle();
142 mVelocityTracker = null;
143 }
144 }
145
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700146 /** Touch preprocessing for handling below */
147 public boolean onInterceptTouchEvent(MotionEvent ev) {
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700148 // Pass through to swipe helper if we are swiping
149 mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
150 if (mInterceptedBySwipeHelper) {
151 return true;
152 }
153
Winson23afcae2015-10-28 11:14:54 -0700154 return handleTouchEvent(ev);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700155 }
156
157 /** Handles touch events once we have intercepted them */
158 public boolean onTouchEvent(MotionEvent ev) {
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700159 // Pass through to swipe helper if we are swiping
160 if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
161 return true;
162 }
163
Winson23afcae2015-10-28 11:14:54 -0700164 handleTouchEvent(ev);
165 return true;
166 }
167
168 private boolean handleTouchEvent(MotionEvent ev) {
169 // Short circuit if we have no children
170 if (mSv.getTaskViews().size() == 0) {
171 return false;
172 }
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700173
Winson36a5a2c2015-10-29 18:04:39 -0700174 final TaskStackLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
Winson147ecaf2015-09-16 16:49:55 -0700175 int action = ev.getAction();
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700176 switch (action & MotionEvent.ACTION_MASK) {
177 case MotionEvent.ACTION_DOWN: {
178 // Save the touch down info
Winson23afcae2015-10-28 11:14:54 -0700179 mDownX = (int) ev.getX();
180 mDownY = (int) ev.getY();
Winson8b1871d2015-11-20 09:56:20 -0800181 mLastY = mDownY;
Winson23afcae2015-10-28 11:14:54 -0700182 mDownScrollP = mScroller.getStackScroll();
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700183 mActivePointerId = ev.getPointerId(0);
Winson23afcae2015-10-28 11:14:54 -0700184 mActiveTaskView = findViewAtPoint(mDownX, mDownY);
185
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700186 // Stop the current scroll if it is still flinging
Winson Chung012ef362014-07-31 18:36:25 -0700187 mScroller.stopScroller();
188 mScroller.stopBoundScrollAnimation();
Winson36a5a2c2015-10-29 18:04:39 -0700189 Utilities.cancelAnimationWithoutCallbacks(mScrollFlingAnimator);
Winson23afcae2015-10-28 11:14:54 -0700190
Winson8aa99592016-01-19 15:07:07 -0800191 // Finish any existing task animations from the delete
192 mSv.cancelAllTaskViewAnimations();
193 // Finish any of the swipe helper animations
194 ArrayMap<View, Animator> existingAnimators = new ArrayMap<>(mSwipeHelperAnimations);
195 for (int i = 0; i < existingAnimators.size(); i++) {
196 existingAnimators.get(existingAnimators.keyAt(i)).end();
197 }
198 mSwipeHelperAnimations.clear();
199
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700200 // Initialize the velocity tracker
201 initOrResetVelocityTracker();
Winson23afcae2015-10-28 11:14:54 -0700202 mVelocityTracker.addMovement(ev);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700203 break;
204 }
205 case MotionEvent.ACTION_POINTER_DOWN: {
206 final int index = ev.getActionIndex();
Winson40149bf2015-12-03 10:07:52 -0800207 mActivePointerId = ev.getPointerId(index);
208 mDownX = (int) ev.getX(index);
209 mDownY = (int) ev.getY(index);
Winson8b1871d2015-11-20 09:56:20 -0800210 mLastY = mDownY;
Winson23afcae2015-10-28 11:14:54 -0700211 mDownScrollP = mScroller.getStackScroll();
Winson23afcae2015-10-28 11:14:54 -0700212 mVelocityTracker.addMovement(ev);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700213 break;
214 }
215 case MotionEvent.ACTION_MOVE: {
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700216 int activePointerIndex = ev.findPointerIndex(mActivePointerId);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700217 int y = (int) ev.getY(activePointerIndex);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700218 if (!mIsScrolling) {
Winson23afcae2015-10-28 11:14:54 -0700219 if (Math.abs(y - mDownY) > mScrollTouchSlop) {
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700220 mIsScrolling = true;
Winson23afcae2015-10-28 11:14:54 -0700221
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 }
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700227 }
228 }
229 if (mIsScrolling) {
Winson23afcae2015-10-28 11:14:54 -0700230 // If we just move linearly on the screen, then that would map to 1/arclength
231 // of the curve, so just move the scroll proportional to that
232 float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
233 float curScrollP = mDownScrollP + deltaP;
234 mScroller.setStackScroll(curScrollP);
Winson55003902016-01-12 12:00:37 -0800235 mStackViewScrolledEvent.updateY(y - mLastY);
236 EventBus.getDefault().send(mStackViewScrolledEvent);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700237 }
238
Winson8b1871d2015-11-20 09:56:20 -0800239 mLastY = y;
Winson23afcae2015-10-28 11:14:54 -0700240 mVelocityTracker.addMovement(ev);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700241 break;
242 }
243 case MotionEvent.ACTION_POINTER_UP: {
244 int pointerIndex = ev.getActionIndex();
245 int pointerId = ev.getPointerId(pointerIndex);
246 if (pointerId == mActivePointerId) {
247 // Select a new active pointer id and reset the motion state
248 final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
249 mActivePointerId = ev.getPointerId(newPointerIndex);
Winson40149bf2015-12-03 10:07:52 -0800250 mDownX = (int) ev.getX(pointerIndex);
251 mDownY = (int) ev.getY(pointerIndex);
252 mLastY = mDownY;
253 mDownScrollP = mScroller.getStackScroll();
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700254 }
Winson23afcae2015-10-28 11:14:54 -0700255 mVelocityTracker.addMovement(ev);
256 break;
257 }
258 case MotionEvent.ACTION_UP: {
259 mVelocityTracker.addMovement(ev);
260 mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
261 int activePointerIndex = ev.findPointerIndex(mActivePointerId);
262 int y = (int) ev.getY(activePointerIndex);
263 int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
264 if (mIsScrolling) {
Winsona5e6b362015-11-02 17:17:20 -0800265 if (mScroller.isScrollOutOfBounds()) {
Winson23afcae2015-10-28 11:14:54 -0700266 mScroller.animateBoundScroll();
267 } else if (Math.abs(velocity) > mMinimumVelocity) {
Winson36a5a2c2015-10-29 18:04:39 -0700268 float minY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
Winsona5e6b362015-11-02 17:17:20 -0800269 layoutAlgorithm.mMaxScrollP);
Winson36a5a2c2015-10-29 18:04:39 -0700270 float maxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
Winson23afcae2015-10-28 11:14:54 -0700271 layoutAlgorithm.mMinScrollP);
Winson36a5a2c2015-10-29 18:04:39 -0700272 mScroller.fling(mDownScrollP, mDownY, y, velocity, (int) minY, (int) maxY,
273 mOverscrollSize);
Winson23afcae2015-10-28 11:14:54 -0700274 mSv.invalidate();
275 }
276 } else if (mActiveTaskView == null) {
277 // This tap didn't start on a task.
278 maybeHideRecentsFromBackgroundTap((int) ev.getX(), (int) ev.getY());
279 }
280
281 mActivePointerId = INACTIVE_POINTER_ID;
282 mIsScrolling = false;
283 recycleVelocityTracker();
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700284 break;
285 }
286 case MotionEvent.ACTION_CANCEL: {
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700287 mActivePointerId = INACTIVE_POINTER_ID;
288 mIsScrolling = false;
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700289 recycleVelocityTracker();
290 break;
291 }
292 }
Winson23afcae2015-10-28 11:14:54 -0700293 return mIsScrolling;
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700294 }
295
James Cook4bd79b72015-03-17 07:33:28 -0700296 /** Hides recents if the up event at (x, y) is a tap on the background area. */
297 void maybeHideRecentsFromBackgroundTap(int x, int y) {
298 // Ignore the up event if it's too far from its start position. The user might have been
299 // trying to scroll or swipe.
Winson23afcae2015-10-28 11:14:54 -0700300 int dx = Math.abs(mDownX - x);
301 int dy = Math.abs(mDownY - y);
James Cook4bd79b72015-03-17 07:33:28 -0700302 if (dx > mScrollTouchSlop || dy > mScrollTouchSlop) {
303 return;
304 }
305
306 // Shift the tap position toward the center of the task stack and check to see if it would
307 // have hit a view. The user might have tried to tap on a task and missed slightly.
308 int shiftedX = x;
Winson147ecaf2015-09-16 16:49:55 -0700309 if (x > (mSv.getRight() - mSv.getLeft()) / 2) {
James Cook4bd79b72015-03-17 07:33:28 -0700310 shiftedX -= mWindowTouchSlop;
311 } else {
312 shiftedX += mWindowTouchSlop;
313 }
314 if (findViewAtPoint(shiftedX, y) != null) {
315 return;
316 }
317
Winsona5e6b362015-11-02 17:17:20 -0800318 // If tapping on the freeform workspace background, just launch the first freeform task
319 SystemServicesProxy ssp = Recents.getSystemServices();
320 if (ssp.hasFreeformWorkspaceSupport()) {
321 Rect freeformRect = mSv.mLayoutAlgorithm.mFreeformRect;
322 if (freeformRect.top <= y && y <= freeformRect.bottom) {
323 if (mSv.launchFreeformTasks()) {
Winsonf24f2162016-01-05 12:11:55 -0800324 // TODO: Animate Recents away as we launch the freeform tasks
Winsona5e6b362015-11-02 17:17:20 -0800325 return;
326 }
327 }
328 }
329
James Cook4bd79b72015-03-17 07:33:28 -0700330 // The user intentionally tapped on the background, which is like a tap on the "desktop".
331 // Hide recents and transition to the launcher.
Winson412e1802015-10-20 16:57:57 -0700332 EventBus.getDefault().send(new HideRecentsEvent(false, true));
James Cook4bd79b72015-03-17 07:33:28 -0700333 }
334
Winson Chungd213a1e2014-10-02 11:18:30 -0700335 /** Handles generic motion events */
336 public boolean onGenericMotionEvent(MotionEvent ev) {
337 if ((ev.getSource() & InputDevice.SOURCE_CLASS_POINTER) ==
338 InputDevice.SOURCE_CLASS_POINTER) {
339 int action = ev.getAction();
340 switch (action & MotionEvent.ACTION_MASK) {
341 case MotionEvent.ACTION_SCROLL:
342 // Find the front most task and scroll the next task to the front
343 float vScroll = ev.getAxisValue(MotionEvent.AXIS_VSCROLL);
344 if (vScroll > 0) {
Winson1b585612015-11-06 09:16:26 -0800345 mSv.setRelativeFocusedTask(true, true /* stackTasksOnly */,
346 false /* animated */);
Winson Chungd213a1e2014-10-02 11:18:30 -0700347 } else {
Winson1b585612015-11-06 09:16:26 -0800348 mSv.setRelativeFocusedTask(false, true /* stackTasksOnly */,
349 false /* animated */);
Winson Chungd213a1e2014-10-02 11:18:30 -0700350 }
351 return true;
352 }
353 }
354 return false;
355 }
356
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700357 /**** SwipeHelper Implementation ****/
358
359 @Override
360 public View getChildAtPosition(MotionEvent ev) {
Winson8aa99592016-01-19 15:07:07 -0800361 TaskView tv = findViewAtPoint((int) ev.getX(), (int) ev.getY());
362 if (tv != null && canChildBeDismissed(tv)) {
363 return tv;
364 }
365 return null;
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700366 }
367
368 @Override
369 public boolean canChildBeDismissed(View v) {
Winson8aa99592016-01-19 15:07:07 -0800370 // Disallow dismissing an already dismissed task
371 TaskView tv = (TaskView) v;
372 return !mSwipeHelperAnimations.containsKey(v) &&
373 (mSv.getStack().indexOfStackTask(tv.getTask()) != -1);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700374 }
375
376 @Override
377 public void onBeginDrag(View v) {
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700378 TaskView tv = (TaskView) v;
Winson671e8f92016-01-12 13:16:56 -0800379
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700380 // Disable clipping with the stack while we are swiping
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700381 tv.setClipViewInStack(false);
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700382 // Disallow touch events from this task view
Winson Chung1f24c7e2014-07-11 17:06:48 -0700383 tv.setTouchEnabled(false);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700384 // Disallow parents from intercepting touch events
385 final ViewParent parent = mSv.getParent();
386 if (parent != null) {
387 parent.requestDisallowInterceptTouchEvent(true);
388 }
Winson8aa99592016-01-19 15:07:07 -0800389
390 // Add this task to the set of tasks we are deleting
391 mSv.addIgnoreTask(tv.getTask());
392
393 // Determine if we are animating the other tasks while dismissing this task
394 mCurrentTasks = mSv.getStack().getStackTasks();
395 MutableBoolean isFrontMostTask = new MutableBoolean(false);
396 Task anchorTask = mSv.findAnchorTask(mCurrentTasks, isFrontMostTask);
397 TaskStackViewScroller stackScroller = mSv.getScroller();
398 if (anchorTask != null) {
399 // Get the current set of task transforms
400 mSv.getCurrentTaskTransforms(mCurrentTasks, mCurrentTaskTransforms);
401
402 // Get the stack scroll of the task to anchor to (since we are removing something, the
403 // front most task will be our anchor task)
404 float prevAnchorTaskScroll = 0;
405 boolean pullStackForward = mCurrentTasks.size() > 0;
406 if (pullStackForward) {
407 prevAnchorTaskScroll = mSv.getStackAlgorithm().getStackScrollForTask(anchorTask);
408 }
409
410 // Calculate where the views would be without the deleting tasks
411 mSv.updateLayoutAlgorithm(false /* boundScroll */);
412
413 float newStackScroll = stackScroller.getStackScroll();
414 if (isFrontMostTask.value) {
415 // Bound the stack scroll to pull tasks forward if necessary
416 newStackScroll = stackScroller.getBoundedStackScroll(newStackScroll);
417 } else if (pullStackForward) {
418 // Otherwise, offset the scroll by the movement of the anchor task
419 float anchorTaskScroll = mSv.getStackAlgorithm().getStackScrollForTask(anchorTask);
420 float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll);
421 if (mSv.getStackAlgorithm().getFocusState() !=
422 TaskStackLayoutAlgorithm.STATE_FOCUSED) {
423 // If we are focused, we don't want the front task to move, but otherwise, we
424 // allow the back task to move up, and the front task to move back
425 stackScrollOffset /= 2;
426 }
427 newStackScroll = stackScroller.getBoundedStackScroll(stackScroller.getStackScroll()
428 + stackScrollOffset);
429 }
430
431 // Pick up the newly visible views, not including the deleting tasks
432 mSv.bindVisibleTaskViews(newStackScroll);
433
434 // Get the final set of task transforms (with task removed)
435 mSv.getLayoutTaskTransforms(newStackScroll, mCurrentTasks, mFinalTaskTransforms);
436
437 // Set the target to scroll towards upon dismissal
438 mTargetStackScroll = newStackScroll;
439
440 /*
441 * Post condition: All views that will be visible as a part of the gesture are retrieved
442 * and at their initial positions. The stack is still at the current
443 * scroll, but the layout is updated without the task currently being
444 * dismissed.
445 */
446 }
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700447 }
448
449 @Override
Winson671e8f92016-01-12 13:16:56 -0800450 public boolean updateSwipeProgress(View v, boolean dismissable, float swipeProgress) {
Winson8aa99592016-01-19 15:07:07 -0800451 updateTaskViewTransforms(getDismissFraction(v));
Winson671e8f92016-01-12 13:16:56 -0800452 return true;
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700453 }
454
Winson8aa99592016-01-19 15:07:07 -0800455 /**
456 * Called after the {@link TaskView} is finished animating away.
457 */
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700458 @Override
459 public void onChildDismissed(View v) {
460 TaskView tv = (TaskView) v;
Winson8aa99592016-01-19 15:07:07 -0800461
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700462 // Re-enable clipping with the stack (we will reuse this view)
463 tv.setClipViewInStack(true);
Winson Chung3e3365b2014-07-17 18:39:02 -0700464 // Re-enable touch events from this task view
465 tv.setTouchEnabled(true);
Winson8aa99592016-01-19 15:07:07 -0800466 // Update the scroll to the final scroll position from onBeginDrag()
467 mSv.getScroller().setStackScroll(mTargetStackScroll, null);
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700468 // Remove the task view from the stack
Winsonef064132016-01-05 12:11:31 -0800469 EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv));
Winson8aa99592016-01-19 15:07:07 -0800470 // Stop tracking this deletion animation
471 mSwipeHelperAnimations.remove(v);
Winson Chung5c9f4b92015-06-25 16:16:46 -0700472 // Keep track of deletions by keyboard
473 MetricsLogger.histogram(tv.getContext(), "overview_task_dismissed_source",
474 Constants.Metrics.DismissSourceSwipeGesture);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700475 }
476
Winson8aa99592016-01-19 15:07:07 -0800477 /**
478 * Called after the {@link TaskView} is finished animating back into the list.
479 * onChildDismissed() calls.
480 */
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700481 @Override
Winson671e8f92016-01-12 13:16:56 -0800482 public void onChildSnappedBack(View v) {
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700483 TaskView tv = (TaskView) v;
Winson8aa99592016-01-19 15:07:07 -0800484
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700485 // Re-enable clipping with the stack
486 tv.setClipViewInStack(true);
487 // Re-enable touch events from this task view
Winson Chung1f24c7e2014-07-11 17:06:48 -0700488 tv.setTouchEnabled(true);
Winson8aa99592016-01-19 15:07:07 -0800489
490 // Stop tracking this deleting task, and update the layout to include this task again. The
491 // stack scroll does not need to be reset, since the scroll has not actually changed in
492 // onBeginDrag().
493 mSv.removeIgnoreTask(tv.getTask());
494 mSv.updateLayoutAlgorithm(false /* boundScroll */);
495 mSwipeHelperAnimations.remove(v);
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700496 }
Winson Chung7aceb9a2014-07-03 13:38:01 -0700497
498 @Override
499 public void onDragCancelled(View v) {
500 // Do nothing
501 }
Winson671e8f92016-01-12 13:16:56 -0800502
503 @Override
504 public View getChildContentView(View v) {
505 return v;
506 }
507
508 @Override
509 public boolean isAntiFalsingNeeded() {
510 return false;
511 }
512
513 @Override
514 public float getFalsingThresholdFactor() {
515 return 0;
516 }
517
Winson8aa99592016-01-19 15:07:07 -0800518 /**
519 * Interpolates the non-deleting tasks to their final transforms from their current transforms.
520 */
521 private void updateTaskViewTransforms(float dismissFraction) {
522 List<TaskView> taskViews = mSv.getTaskViews();
523 int taskViewCount = taskViews.size();
524 for (int i = 0; i < taskViewCount; i++) {
525 TaskView tv = taskViews.get(i);
526 Task task = tv.getTask();
527
528 if (mSv.isIgnoredTask(task)) {
529 continue;
530 }
531
532 int taskIndex = mCurrentTasks.indexOf(task);
533 TaskViewTransform fromTransform = mCurrentTaskTransforms.get(taskIndex);
534 TaskViewTransform toTransform = mFinalTaskTransforms.get(taskIndex);
535
536 mTmpTransform.copyFrom(fromTransform);
537 // We only really need to interpolate the bounds, progress and translation
538 mTmpTransform.rect.set(RECT_EVALUATOR.evaluate(dismissFraction, fromTransform.rect,
539 toTransform.rect));
540 mTmpTransform.p = fromTransform.p + (toTransform.p - fromTransform.p) * dismissFraction;
541 mTmpTransform.translationZ = fromTransform.translationZ +
542 (toTransform.translationZ - fromTransform.translationZ) * dismissFraction;
543
544 mSv.updateTaskViewToTransform(tv, mTmpTransform, TaskViewAnimation.IMMEDIATE);
545 }
546 }
547
548 /** Returns the view at the specified coordinates */
549 private TaskView findViewAtPoint(int x, int y) {
550 List<Task> tasks = mSv.getStack().getStackTasks();
551 int taskCount = tasks.size();
552 for (int i = taskCount - 1; i >= 0; i--) {
553 TaskView tv = mSv.getChildViewForTask(tasks.get(i));
554 if (tv != null && tv.getVisibility() == View.VISIBLE) {
555 if (mSv.isTouchPointInView(x, y, tv)) {
556 return tv;
557 }
558 }
559 }
560 return null;
561 }
562
563 /**
564 * Returns the fraction which we should interpolate the other task views based on the dismissal
565 * of this given task.
566 *
567 * TODO: We can interpolate this to adjust when the other tasks should respond to the dismissal
568 */
569 private float getDismissFraction(View v) {
570 float fraction = Math.min(1f, Math.abs(v.getTranslationX() / mSv.getWidth()));
571 return STACK_TRANSFORM_INTERPOLATOR.getInterpolation(fraction);
572 }
Winson Chungcdbbb7e2014-06-24 12:11:49 -0700573}