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