Home | History | Annotate | Download | only in views
      1 /*
      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 
     17 package com.android.systemui.recents.views;
     18 
     19 import android.animation.Animator;
     20 import android.animation.ValueAnimator;
     21 import android.content.Context;
     22 import android.content.res.Resources;
     23 import android.graphics.Path;
     24 import android.graphics.Rect;
     25 import android.util.ArrayMap;
     26 import android.util.MutableBoolean;
     27 import android.view.InputDevice;
     28 import android.view.MotionEvent;
     29 import android.view.VelocityTracker;
     30 import android.view.View;
     31 import android.view.ViewConfiguration;
     32 import android.view.ViewDebug;
     33 import android.view.ViewParent;
     34 import android.view.animation.Interpolator;
     35 
     36 import com.android.internal.logging.MetricsLogger;
     37 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     38 import com.android.systemui.Interpolators;
     39 import com.android.systemui.R;
     40 import com.android.systemui.SwipeHelper;
     41 import com.android.systemui.recents.Constants;
     42 import com.android.systemui.recents.Recents;
     43 import com.android.systemui.recents.events.EventBus;
     44 import com.android.systemui.recents.events.activity.HideRecentsEvent;
     45 import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
     46 import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
     47 import com.android.systemui.recents.misc.FreePathInterpolator;
     48 import com.android.systemui.recents.misc.SystemServicesProxy;
     49 import com.android.systemui.recents.misc.Utilities;
     50 import com.android.systemui.recents.model.Task;
     51 import com.android.systemui.statusbar.FlingAnimationUtils;
     52 
     53 import java.util.ArrayList;
     54 import java.util.List;
     55 
     56 /**
     57  * Handles touch events for a TaskStackView.
     58  */
     59 class TaskStackViewTouchHandler implements SwipeHelper.Callback {
     60 
     61     private static final int INACTIVE_POINTER_ID = -1;
     62     private static final float CHALLENGING_SWIPE_ESCAPE_VELOCITY = 800f; // dp/sec
     63     // The min overscroll is the amount of task progress overscroll we want / the max overscroll
     64     // curve value below
     65     private static final float MAX_OVERSCROLL = 0.7f / 0.3f;
     66     private static final Interpolator OVERSCROLL_INTERP;
     67     static {
     68         Path OVERSCROLL_PATH = new Path();
     69         OVERSCROLL_PATH.moveTo(0, 0);
     70         OVERSCROLL_PATH.cubicTo(0.2f, 0.175f, 0.25f, 0.3f, 1f, 0.3f);
     71         OVERSCROLL_INTERP = new FreePathInterpolator(OVERSCROLL_PATH);
     72     }
     73 
     74     Context mContext;
     75     TaskStackView mSv;
     76     TaskStackViewScroller mScroller;
     77     VelocityTracker mVelocityTracker;
     78     FlingAnimationUtils mFlingAnimUtils;
     79     ValueAnimator mScrollFlingAnimator;
     80 
     81     @ViewDebug.ExportedProperty(category="recents")
     82     boolean mIsScrolling;
     83     float mDownScrollP;
     84     int mDownX, mDownY;
     85     int mLastY;
     86     int mActivePointerId = INACTIVE_POINTER_ID;
     87     int mOverscrollSize;
     88     TaskView mActiveTaskView = null;
     89 
     90     int mMinimumVelocity;
     91     int mMaximumVelocity;
     92     // The scroll touch slop is used to calculate when we start scrolling
     93     int mScrollTouchSlop;
     94     // Used to calculate when a tap is outside a task view rectangle.
     95     final int mWindowTouchSlop;
     96 
     97     private final StackViewScrolledEvent mStackViewScrolledEvent = new StackViewScrolledEvent();
     98 
     99     // The current and final set of task transforms, sized to match the list of tasks in the stack
    100     private ArrayList<Task> mCurrentTasks = new ArrayList<>();
    101     private ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
    102     private ArrayList<TaskViewTransform> mFinalTaskTransforms = new ArrayList<>();
    103     private ArrayMap<View, Animator> mSwipeHelperAnimations = new ArrayMap<>();
    104     private TaskViewTransform mTmpTransform = new TaskViewTransform();
    105     private float mTargetStackScroll;
    106 
    107     SwipeHelper mSwipeHelper;
    108     boolean mInterceptedBySwipeHelper;
    109 
    110     public TaskStackViewTouchHandler(Context context, TaskStackView sv,
    111             TaskStackViewScroller scroller) {
    112         Resources res = context.getResources();
    113         ViewConfiguration configuration = ViewConfiguration.get(context);
    114         mContext = context;
    115         mSv = sv;
    116         mScroller = scroller;
    117         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
    118         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    119         mScrollTouchSlop = configuration.getScaledTouchSlop();
    120         mWindowTouchSlop = configuration.getScaledWindowTouchSlop();
    121         mFlingAnimUtils = new FlingAnimationUtils(context, 0.2f);
    122         mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_fling_overscroll_distance);
    123         mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, context) {
    124             @Override
    125             protected float getSize(View v) {
    126                 return getScaledDismissSize();
    127             }
    128 
    129             @Override
    130             protected void prepareDismissAnimation(View v, Animator anim) {
    131                 mSwipeHelperAnimations.put(v, anim);
    132             }
    133 
    134             @Override
    135             protected void prepareSnapBackAnimation(View v, Animator anim) {
    136                 anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
    137                 mSwipeHelperAnimations.put(v, anim);
    138             }
    139 
    140             @Override
    141             protected float getUnscaledEscapeVelocity() {
    142                 return CHALLENGING_SWIPE_ESCAPE_VELOCITY;
    143             }
    144 
    145             @Override
    146             protected long getMaxEscapeAnimDuration() {
    147                 return 700;
    148             }
    149         };
    150         mSwipeHelper.setDisableHardwareLayers(true);
    151     }
    152 
    153     /** Velocity tracker helpers */
    154     void initOrResetVelocityTracker() {
    155         if (mVelocityTracker == null) {
    156             mVelocityTracker = VelocityTracker.obtain();
    157         } else {
    158             mVelocityTracker.clear();
    159         }
    160     }
    161     void recycleVelocityTracker() {
    162         if (mVelocityTracker != null) {
    163             mVelocityTracker.recycle();
    164             mVelocityTracker = null;
    165         }
    166     }
    167 
    168     /** Touch preprocessing for handling below */
    169     public boolean onInterceptTouchEvent(MotionEvent ev) {
    170         // Pass through to swipe helper if we are swiping
    171         mInterceptedBySwipeHelper = isSwipingEnabled() && mSwipeHelper.onInterceptTouchEvent(ev);
    172         if (mInterceptedBySwipeHelper) {
    173             return true;
    174         }
    175 
    176         return handleTouchEvent(ev);
    177     }
    178 
    179     /** Handles touch events once we have intercepted them */
    180     public boolean onTouchEvent(MotionEvent ev) {
    181         // Pass through to swipe helper if we are swiping
    182         if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
    183             return true;
    184         }
    185 
    186         handleTouchEvent(ev);
    187         return true;
    188     }
    189 
    190     /**
    191      * Finishes all scroll-fling and non-dismissing animations currently running.
    192      */
    193     public void cancelNonDismissTaskAnimations() {
    194         Utilities.cancelAnimationWithoutCallbacks(mScrollFlingAnimator);
    195         if (!mSwipeHelperAnimations.isEmpty()) {
    196             // For the non-dismissing tasks, freeze the position into the task overrides
    197             List<TaskView> taskViews = mSv.getTaskViews();
    198             for (int i = taskViews.size() - 1; i >= 0; i--) {
    199                 TaskView tv = taskViews.get(i);
    200 
    201                 if (mSv.isIgnoredTask(tv.getTask())) {
    202                     continue;
    203                 }
    204 
    205                 tv.cancelTransformAnimation();
    206                 mSv.getStackAlgorithm().addUnfocusedTaskOverride(tv, mTargetStackScroll);
    207             }
    208             mSv.getStackAlgorithm().setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED);
    209             // Update the scroll to the final scroll position from onBeginDrag()
    210             mSv.getScroller().setStackScroll(mTargetStackScroll, null);
    211 
    212             mSwipeHelperAnimations.clear();
    213         }
    214         mActiveTaskView = null;
    215     }
    216 
    217     private boolean handleTouchEvent(MotionEvent ev) {
    218         // Short circuit if we have no children
    219         if (mSv.getTaskViews().size() == 0) {
    220             return false;
    221         }
    222 
    223         final TaskStackLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
    224         int action = ev.getAction();
    225         switch (action & MotionEvent.ACTION_MASK) {
    226             case MotionEvent.ACTION_DOWN: {
    227                 // Stop the current scroll if it is still flinging
    228                 mScroller.stopScroller();
    229                 mScroller.stopBoundScrollAnimation();
    230                 mScroller.resetDeltaScroll();
    231                 cancelNonDismissTaskAnimations();
    232                 mSv.cancelDeferredTaskViewLayoutAnimation();
    233 
    234                 // Save the touch down info
    235                 mDownX = (int) ev.getX();
    236                 mDownY = (int) ev.getY();
    237                 mLastY = mDownY;
    238                 mDownScrollP = mScroller.getStackScroll();
    239                 mActivePointerId = ev.getPointerId(0);
    240                 mActiveTaskView = findViewAtPoint(mDownX, mDownY);
    241 
    242                 // Initialize the velocity tracker
    243                 initOrResetVelocityTracker();
    244                 mVelocityTracker.addMovement(ev);
    245                 break;
    246             }
    247             case MotionEvent.ACTION_POINTER_DOWN: {
    248                 final int index = ev.getActionIndex();
    249                 mActivePointerId = ev.getPointerId(index);
    250                 mDownX = (int) ev.getX(index);
    251                 mDownY = (int) ev.getY(index);
    252                 mLastY = mDownY;
    253                 mDownScrollP = mScroller.getStackScroll();
    254                 mScroller.resetDeltaScroll();
    255                 mVelocityTracker.addMovement(ev);
    256                 break;
    257             }
    258             case MotionEvent.ACTION_MOVE: {
    259                 int activePointerIndex = ev.findPointerIndex(mActivePointerId);
    260                 int y = (int) ev.getY(activePointerIndex);
    261                 int x = (int) ev.getX(activePointerIndex);
    262                 if (!mIsScrolling) {
    263                     int yDiff = Math.abs(y - mDownY);
    264                     int xDiff = Math.abs(x - mDownX);
    265                     if (Math.abs(y - mDownY) > mScrollTouchSlop && yDiff > xDiff) {
    266                         mIsScrolling = true;
    267                         float stackScroll = mScroller.getStackScroll();
    268                         List<TaskView> taskViews = mSv.getTaskViews();
    269                         for (int i = taskViews.size() - 1; i >= 0; i--) {
    270                             layoutAlgorithm.addUnfocusedTaskOverride(taskViews.get(i).getTask(),
    271                                     stackScroll);
    272                         }
    273                         layoutAlgorithm.setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED);
    274 
    275                         // Disallow parents from intercepting touch events
    276                         final ViewParent parent = mSv.getParent();
    277                         if (parent != null) {
    278                             parent.requestDisallowInterceptTouchEvent(true);
    279                         }
    280 
    281                         MetricsLogger.action(mSv.getContext(), MetricsEvent.OVERVIEW_SCROLL);
    282                         mLastY = mDownY = y;
    283                     }
    284                 }
    285                 if (mIsScrolling) {
    286                     // If we just move linearly on the screen, then that would map to 1/arclength
    287                     // of the curve, so just move the scroll proportional to that
    288                     float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
    289 
    290                     // Modulate the overscroll to prevent users from pulling the stack too far
    291                     float minScrollP = layoutAlgorithm.mMinScrollP;
    292                     float maxScrollP = layoutAlgorithm.mMaxScrollP;
    293                     float curScrollP = mDownScrollP + deltaP;
    294                     if (curScrollP < minScrollP || curScrollP > maxScrollP) {
    295                         float clampedScrollP = Utilities.clamp(curScrollP, minScrollP, maxScrollP);
    296                         float overscrollP = (curScrollP - clampedScrollP);
    297                         float overscrollX = Math.abs(overscrollP) / MAX_OVERSCROLL;
    298                         float interpX = OVERSCROLL_INTERP.getInterpolation(overscrollX);
    299                         curScrollP = clampedScrollP + Math.signum(overscrollP) *
    300                                 (interpX * MAX_OVERSCROLL);
    301                     }
    302                     mDownScrollP += mScroller.setDeltaStackScroll(mDownScrollP,
    303                             curScrollP - mDownScrollP);
    304                     mStackViewScrolledEvent.updateY(y - mLastY);
    305                     EventBus.getDefault().send(mStackViewScrolledEvent);
    306                 }
    307 
    308                 mLastY = y;
    309                 mVelocityTracker.addMovement(ev);
    310                 break;
    311             }
    312             case MotionEvent.ACTION_POINTER_UP: {
    313                 int pointerIndex = ev.getActionIndex();
    314                 int pointerId = ev.getPointerId(pointerIndex);
    315                 if (pointerId == mActivePointerId) {
    316                     // Select a new active pointer id and reset the motion state
    317                     final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
    318                     mActivePointerId = ev.getPointerId(newPointerIndex);
    319                     mDownX = (int) ev.getX(pointerIndex);
    320                     mDownY = (int) ev.getY(pointerIndex);
    321                     mLastY = mDownY;
    322                     mDownScrollP = mScroller.getStackScroll();
    323                 }
    324                 mVelocityTracker.addMovement(ev);
    325                 break;
    326             }
    327             case MotionEvent.ACTION_UP: {
    328                 mVelocityTracker.addMovement(ev);
    329                 mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
    330                 int activePointerIndex = ev.findPointerIndex(mActivePointerId);
    331                 int y = (int) ev.getY(activePointerIndex);
    332                 int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
    333                 if (mIsScrolling) {
    334                     if (mScroller.isScrollOutOfBounds()) {
    335                         mScroller.animateBoundScroll();
    336                     } else if (Math.abs(velocity) > mMinimumVelocity) {
    337                         float minY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
    338                                 layoutAlgorithm.mMaxScrollP);
    339                         float maxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
    340                                 layoutAlgorithm.mMinScrollP);
    341                         mScroller.fling(mDownScrollP, mDownY, y, velocity, (int) minY, (int) maxY,
    342                                 mOverscrollSize);
    343                         mSv.invalidate();
    344                     }
    345 
    346                     // Reset the focused task after the user has scrolled, but we have no scrolling
    347                     // in grid layout and therefore we don't want to reset the focus there.
    348                     if (!mSv.mTouchExplorationEnabled && !mSv.useGridLayout()) {
    349                         mSv.resetFocusedTask(mSv.getFocusedTask());
    350                     }
    351                 } else if (mActiveTaskView == null) {
    352                     // This tap didn't start on a task.
    353                     maybeHideRecentsFromBackgroundTap((int) ev.getX(), (int) ev.getY());
    354                 }
    355 
    356                 mActivePointerId = INACTIVE_POINTER_ID;
    357                 mIsScrolling = false;
    358                 recycleVelocityTracker();
    359                 break;
    360             }
    361             case MotionEvent.ACTION_CANCEL: {
    362                 mActivePointerId = INACTIVE_POINTER_ID;
    363                 mIsScrolling = false;
    364                 recycleVelocityTracker();
    365                 break;
    366             }
    367         }
    368         return mIsScrolling;
    369     }
    370 
    371     /** Hides recents if the up event at (x, y) is a tap on the background area. */
    372     void maybeHideRecentsFromBackgroundTap(int x, int y) {
    373         // Ignore the up event if it's too far from its start position. The user might have been
    374         // trying to scroll or swipe.
    375         int dx = Math.abs(mDownX - x);
    376         int dy = Math.abs(mDownY - y);
    377         if (dx > mScrollTouchSlop || dy > mScrollTouchSlop) {
    378             return;
    379         }
    380 
    381         // Shift the tap position toward the center of the task stack and check to see if it would
    382         // have hit a view. The user might have tried to tap on a task and missed slightly.
    383         int shiftedX = x;
    384         if (x > (mSv.getRight() - mSv.getLeft()) / 2) {
    385             shiftedX -= mWindowTouchSlop;
    386         } else {
    387             shiftedX += mWindowTouchSlop;
    388         }
    389         if (findViewAtPoint(shiftedX, y) != null) {
    390             return;
    391         }
    392 
    393         // Disallow tapping above and below the stack to dismiss recents
    394         if (x > mSv.mLayoutAlgorithm.mStackRect.left && x < mSv.mLayoutAlgorithm.mStackRect.right) {
    395             return;
    396         }
    397 
    398         // If tapping on the freeform workspace background, just launch the first freeform task
    399         SystemServicesProxy ssp = Recents.getSystemServices();
    400         if (ssp.hasFreeformWorkspaceSupport()) {
    401             Rect freeformRect = mSv.mLayoutAlgorithm.mFreeformRect;
    402             if (freeformRect.top <= y && y <= freeformRect.bottom) {
    403                 if (mSv.launchFreeformTasks()) {
    404                     // TODO: Animate Recents away as we launch the freeform tasks
    405                     return;
    406                 }
    407             }
    408         }
    409 
    410         // The user intentionally tapped on the background, which is like a tap on the "desktop".
    411         // Hide recents and transition to the launcher.
    412         EventBus.getDefault().send(new HideRecentsEvent(false, true));
    413     }
    414 
    415     /** Handles generic motion events */
    416     public boolean onGenericMotionEvent(MotionEvent ev) {
    417         if ((ev.getSource() & InputDevice.SOURCE_CLASS_POINTER) ==
    418                 InputDevice.SOURCE_CLASS_POINTER) {
    419             int action = ev.getAction();
    420             switch (action & MotionEvent.ACTION_MASK) {
    421                 case MotionEvent.ACTION_SCROLL:
    422                     // Find the front most task and scroll the next task to the front
    423                     float vScroll = ev.getAxisValue(MotionEvent.AXIS_VSCROLL);
    424                     if (vScroll > 0) {
    425                         mSv.setRelativeFocusedTask(true, true /* stackTasksOnly */,
    426                                 false /* animated */);
    427                     } else {
    428                         mSv.setRelativeFocusedTask(false, true /* stackTasksOnly */,
    429                                 false /* animated */);
    430                     }
    431                     return true;
    432             }
    433         }
    434         return false;
    435     }
    436 
    437     /**** SwipeHelper Implementation ****/
    438 
    439     @Override
    440     public View getChildAtPosition(MotionEvent ev) {
    441         TaskView tv = findViewAtPoint((int) ev.getX(), (int) ev.getY());
    442         if (tv != null && canChildBeDismissed(tv)) {
    443             return tv;
    444         }
    445         return null;
    446     }
    447 
    448     @Override
    449     public boolean canChildBeDismissed(View v) {
    450         // Disallow dismissing an already dismissed task
    451         TaskView tv = (TaskView) v;
    452         Task task = tv.getTask();
    453         return !mSwipeHelperAnimations.containsKey(v) &&
    454                 (mSv.getStack().indexOfStackTask(task) != -1);
    455     }
    456 
    457     /**
    458      * Starts a manual drag that goes through the same swipe helper path.
    459      */
    460     public void onBeginManualDrag(TaskView v) {
    461         mActiveTaskView = v;
    462         mSwipeHelperAnimations.put(v, null);
    463         onBeginDrag(v);
    464     }
    465 
    466     @Override
    467     public void onBeginDrag(View v) {
    468         TaskView tv = (TaskView) v;
    469 
    470         // Disable clipping with the stack while we are swiping
    471         tv.setClipViewInStack(false);
    472         // Disallow touch events from this task view
    473         tv.setTouchEnabled(false);
    474         // Disallow parents from intercepting touch events
    475         final ViewParent parent = mSv.getParent();
    476         if (parent != null) {
    477             parent.requestDisallowInterceptTouchEvent(true);
    478         }
    479 
    480         // Add this task to the set of tasks we are deleting
    481         mSv.addIgnoreTask(tv.getTask());
    482 
    483         // Determine if we are animating the other tasks while dismissing this task
    484         mCurrentTasks = new ArrayList<Task>(mSv.getStack().getStackTasks());
    485         MutableBoolean isFrontMostTask = new MutableBoolean(false);
    486         Task anchorTask = mSv.findAnchorTask(mCurrentTasks, isFrontMostTask);
    487         TaskStackLayoutAlgorithm layoutAlgorithm = mSv.getStackAlgorithm();
    488         TaskStackViewScroller stackScroller = mSv.getScroller();
    489         if (anchorTask != null) {
    490             // Get the current set of task transforms
    491             mSv.getCurrentTaskTransforms(mCurrentTasks, mCurrentTaskTransforms);
    492 
    493             // Get the stack scroll of the task to anchor to (since we are removing something, the
    494             // front most task will be our anchor task)
    495             float prevAnchorTaskScroll = 0;
    496             boolean pullStackForward = mCurrentTasks.size() > 0;
    497             if (pullStackForward) {
    498                 prevAnchorTaskScroll = layoutAlgorithm.getStackScrollForTask(anchorTask);
    499             }
    500 
    501             // Calculate where the views would be without the deleting tasks
    502             mSv.updateLayoutAlgorithm(false /* boundScroll */);
    503 
    504             float newStackScroll = stackScroller.getStackScroll();
    505             if (isFrontMostTask.value) {
    506                 // Bound the stack scroll to pull tasks forward if necessary
    507                 newStackScroll = stackScroller.getBoundedStackScroll(newStackScroll);
    508             } else if (pullStackForward) {
    509                 // Otherwise, offset the scroll by the movement of the anchor task
    510                 float anchorTaskScroll =
    511                         layoutAlgorithm.getStackScrollForTaskIgnoreOverrides(anchorTask);
    512                 float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll);
    513                 if (layoutAlgorithm.getFocusState() != TaskStackLayoutAlgorithm.STATE_FOCUSED) {
    514                     // If we are focused, we don't want the front task to move, but otherwise, we
    515                     // allow the back task to move up, and the front task to move back
    516                     stackScrollOffset *= 0.75f;
    517                 }
    518                 newStackScroll = stackScroller.getBoundedStackScroll(stackScroller.getStackScroll()
    519                         + stackScrollOffset);
    520             }
    521 
    522             // Pick up the newly visible views, not including the deleting tasks
    523             mSv.bindVisibleTaskViews(newStackScroll, true /* ignoreTaskOverrides */);
    524 
    525             // Get the final set of task transforms (with task removed)
    526             mSv.getLayoutTaskTransforms(newStackScroll, TaskStackLayoutAlgorithm.STATE_UNFOCUSED,
    527                     mCurrentTasks, true /* ignoreTaskOverrides */, mFinalTaskTransforms);
    528 
    529             // Set the target to scroll towards upon dismissal
    530             mTargetStackScroll = newStackScroll;
    531 
    532             /*
    533              * Post condition: All views that will be visible as a part of the gesture are retrieved
    534              *                 and at their initial positions.  The stack is still at the current
    535              *                 scroll, but the layout is updated without the task currently being
    536              *                 dismissed.  The final layout is in the unfocused stack state, which
    537              *                 will be applied when the current task is dismissed.
    538              */
    539         }
    540     }
    541 
    542     @Override
    543     public boolean updateSwipeProgress(View v, boolean dismissable, float swipeProgress) {
    544         // Only update the swipe progress for the surrounding tasks if the dismiss animation was not
    545         // preempted from a call to cancelNonDismissTaskAnimations
    546         if (mActiveTaskView == v || mSwipeHelperAnimations.containsKey(v)) {
    547             updateTaskViewTransforms(
    548                     Interpolators.FAST_OUT_SLOW_IN.getInterpolation(swipeProgress));
    549         }
    550         return true;
    551     }
    552 
    553     /**
    554      * Called after the {@link TaskView} is finished animating away.
    555      */
    556     @Override
    557     public void onChildDismissed(View v) {
    558         TaskView tv = (TaskView) v;
    559 
    560         // Re-enable clipping with the stack (we will reuse this view)
    561         tv.setClipViewInStack(true);
    562         // Re-enable touch events from this task view
    563         tv.setTouchEnabled(true);
    564         // Remove the task view from the stack, ignoring the animation if we've started dragging
    565         // again
    566         EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv,
    567                 mSwipeHelperAnimations.containsKey(v)
    568                     ? new AnimationProps(TaskStackView.DEFAULT_SYNC_STACK_DURATION,
    569                         Interpolators.FAST_OUT_SLOW_IN)
    570                     : null));
    571         // Only update the final scroll and layout state (set in onBeginDrag()) if the dismiss
    572         // animation was not preempted from a call to cancelNonDismissTaskAnimations
    573         if (mSwipeHelperAnimations.containsKey(v)) {
    574             // Update the scroll to the final scroll position
    575             mSv.getScroller().setStackScroll(mTargetStackScroll, null);
    576             // Update the focus state to the final focus state
    577             mSv.getStackAlgorithm().setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED);
    578             mSv.getStackAlgorithm().clearUnfocusedTaskOverrides();
    579             // Stop tracking this deletion animation
    580             mSwipeHelperAnimations.remove(v);
    581         }
    582         // Keep track of deletions by keyboard
    583         MetricsLogger.histogram(tv.getContext(), "overview_task_dismissed_source",
    584                 Constants.Metrics.DismissSourceSwipeGesture);
    585     }
    586 
    587     /**
    588      * Called after the {@link TaskView} is finished animating back into the list.
    589      * onChildDismissed() calls.
    590      */
    591     @Override
    592     public void onChildSnappedBack(View v, float targetLeft) {
    593         TaskView tv = (TaskView) v;
    594 
    595         // Re-enable clipping with the stack
    596         tv.setClipViewInStack(true);
    597         // Re-enable touch events from this task view
    598         tv.setTouchEnabled(true);
    599 
    600         // Stop tracking this deleting task, and update the layout to include this task again.  The
    601         // stack scroll does not need to be reset, since the scroll has not actually changed in
    602         // onBeginDrag().
    603         mSv.removeIgnoreTask(tv.getTask());
    604         mSv.updateLayoutAlgorithm(false /* boundScroll */);
    605         mSv.relayoutTaskViews(AnimationProps.IMMEDIATE);
    606         mSwipeHelperAnimations.remove(v);
    607     }
    608 
    609     @Override
    610     public void onDragCancelled(View v) {
    611         // Do nothing
    612     }
    613 
    614     @Override
    615     public boolean isAntiFalsingNeeded() {
    616         return false;
    617     }
    618 
    619     @Override
    620     public float getFalsingThresholdFactor() {
    621         return 0;
    622     }
    623 
    624     /**
    625      * Interpolates the non-deleting tasks to their final transforms from their current transforms.
    626      */
    627     private void updateTaskViewTransforms(float dismissFraction) {
    628         List<TaskView> taskViews = mSv.getTaskViews();
    629         int taskViewCount = taskViews.size();
    630         for (int i = 0; i < taskViewCount; i++) {
    631             TaskView tv = taskViews.get(i);
    632             Task task = tv.getTask();
    633 
    634             if (mSv.isIgnoredTask(task)) {
    635                 continue;
    636             }
    637 
    638             int taskIndex = mCurrentTasks.indexOf(task);
    639             if (taskIndex == -1) {
    640                 // If a task was added to the stack view after the start of the dismiss gesture,
    641                 // just ignore it
    642                 continue;
    643             }
    644 
    645             TaskViewTransform fromTransform = mCurrentTaskTransforms.get(taskIndex);
    646             TaskViewTransform toTransform = mFinalTaskTransforms.get(taskIndex);
    647 
    648             mTmpTransform.copyFrom(fromTransform);
    649             // We only really need to interpolate the bounds, progress and translation
    650             mTmpTransform.rect.set(Utilities.RECTF_EVALUATOR.evaluate(dismissFraction,
    651                     fromTransform.rect, toTransform.rect));
    652             mTmpTransform.dimAlpha = fromTransform.dimAlpha + (toTransform.dimAlpha -
    653                     fromTransform.dimAlpha) * dismissFraction;
    654             mTmpTransform.viewOutlineAlpha = fromTransform.viewOutlineAlpha +
    655                     (toTransform.viewOutlineAlpha - fromTransform.viewOutlineAlpha) *
    656                             dismissFraction;
    657             mTmpTransform.translationZ = fromTransform.translationZ +
    658                     (toTransform.translationZ - fromTransform.translationZ) * dismissFraction;
    659 
    660             mSv.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE);
    661         }
    662     }
    663 
    664     /** Returns the view at the specified coordinates */
    665     private TaskView findViewAtPoint(int x, int y) {
    666         List<Task> tasks = mSv.getStack().getStackTasks();
    667         int taskCount = tasks.size();
    668         for (int i = taskCount - 1; i >= 0; i--) {
    669             TaskView tv = mSv.getChildViewForTask(tasks.get(i));
    670             if (tv != null && tv.getVisibility() == View.VISIBLE) {
    671                 if (mSv.isTouchPointInView(x, y, tv)) {
    672                     return tv;
    673                 }
    674             }
    675         }
    676         return null;
    677     }
    678 
    679     /**
    680      * Returns the scaled size used to calculate the dismiss fraction.
    681      */
    682     public float getScaledDismissSize() {
    683         return 1.5f * Math.max(mSv.getWidth(), mSv.getHeight());
    684     }
    685 
    686     /**
    687      * Returns whether swiping is enabled.
    688      */
    689     private boolean isSwipingEnabled() {
    690         return !mSv.useGridLayout();
    691     }
    692 }
    693