Merge "Animate scrolling grid into place when there is a gap between last tasks and clear all." into sc-v2-dev
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index b9a9006..22c87b0 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -39,10 +39,24 @@
                 }
             };
 
+    public static final FloatProperty<ClearAllButton> DISMISS_ALPHA =
+            new FloatProperty<ClearAllButton>("dismissAlpha") {
+                @Override
+                public Float get(ClearAllButton view) {
+                    return view.mDismissAlpha;
+                }
+
+                @Override
+                public void setValue(ClearAllButton view, float v) {
+                    view.setDismissAlpha(v);
+                }
+            };
+
     private final StatefulActivity mActivity;
     private float mScrollAlpha = 1;
     private float mContentAlpha = 1;
     private float mVisibilityAlpha = 1;
+    private float mDismissAlpha = 1;
     private float mFullscreenProgress = 1;
     private float mGridProgress = 1;
 
@@ -97,6 +111,13 @@
         }
     }
 
+    public void setDismissAlpha(float alpha) {
+        if (mDismissAlpha != alpha) {
+            mDismissAlpha = alpha;
+            updateAlpha();
+        }
+    }
+
     public void onRecentsViewScroll(int scroll, boolean gridEnabled) {
         RecentsView recentsView = getRecentsView();
         if (recentsView == null) {
@@ -123,7 +144,7 @@
     }
 
     private void updateAlpha() {
-        final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha;
+        final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha * mDismissAlpha;
         setAlpha(alpha);
         setClickable(Math.min(alpha, 1) == 1);
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 78b3d4f..8c37644 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -29,6 +29,7 @@
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
+import static com.android.launcher3.Utilities.boundToRange;
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
@@ -49,6 +50,7 @@
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
@@ -359,6 +361,7 @@
     private static final float INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.55f;
     private static final float ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.05f;
     private static final float ANIMATION_DISMISS_PROGRESS_MIDPOINT = 0.5f;
+    private static final float END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.75f;
 
     private static final float SIGNIFICANT_MOVE_THRESHOLD_TABLET = 0.15f;
 
@@ -1004,6 +1007,43 @@
         }
     }
 
+    private boolean isLastGridTaskVisible() {
+        TaskView lastTaskView = getLastGridTaskView();
+        return lastTaskView != null && lastTaskView.isVisibleToUser();
+    }
+
+    private TaskView getLastGridTaskView() {
+        IntArray topRowIdArray = getTopRowIdArray();
+        IntArray bottomRowIdArray = getBottomRowIdArray();
+        if (topRowIdArray.isEmpty() && bottomRowIdArray.isEmpty()) {
+            return null;
+        }
+        int lastTaskViewId = topRowIdArray.size() >= bottomRowIdArray.size() ? topRowIdArray.get(
+                topRowIdArray.size() - 1) : bottomRowIdArray.get(bottomRowIdArray.size() - 1);
+        return getTaskViewFromTaskViewId(lastTaskViewId);
+    }
+
+    private int getSnapToLastTaskScrollDiff() {
+        // Snap to a position where ClearAll is just invisible.
+        int screenStart = mOrientationHandler.getPrimaryScroll(this);
+        int clearAllWidth = mOrientationHandler.getPrimarySize(mClearAllButton);
+        int clearAllScroll = getScrollForPage(indexOfChild(mClearAllButton));
+        int targetScroll = clearAllScroll + (mIsRtl ? clearAllWidth : -clearAllWidth);
+        return screenStart - targetScroll;
+    }
+
+    private int getSnapToFocusedTaskScrollDiff(boolean isClearAllHidden) {
+        int screenStart = mOrientationHandler.getPrimaryScroll(this);
+        int targetScroll = getScrollForPage(indexOfChild(getFocusedTaskView()));
+        if (!isClearAllHidden) {
+            int clearAllWidth = mOrientationHandler.getPrimarySize(mClearAllButton);
+            int taskGridHorizontalDiff = mLastComputedTaskSize.right - mLastComputedGridSize.right;
+            int clearAllFocusScrollDiff =  taskGridHorizontalDiff - clearAllWidth;
+            targetScroll += mIsRtl ? clearAllFocusScrollDiff : -clearAllFocusScrollDiff;
+        }
+        return screenStart - targetScroll;
+    }
+
     private boolean isTaskViewWithinBounds(TaskView tv, int start, int end) {
         int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment(
                 showAsFullscreen(), showAsGrid());
@@ -1083,7 +1123,6 @@
         super.onPageEndTransition();
         if (isClearAllHidden()) {
             mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
-        } else {
         }
         if (getNextPage() > 0) {
             setSwipeDownShouldLaunchApp(true);
@@ -2678,6 +2717,94 @@
             }
         }
 
+        float dismissTranslationInterpolationEnd = 1;
+        boolean closeGapBetweenClearAll = false;
+        boolean isClearAllHidden = isClearAllHidden();
+        if (showAsGrid && isLastGridTaskVisible()) {
+            // After dismissal, animate translation of the remaining tasks to fill any gap left
+            // between the end of the grid and the clear all button. Only animate if the clear
+            // all button is visible or would become visible after dismissal.
+            float longGridRowWidthDiff = 0;
+
+            int topGridRowSize = mTopRowIdSet.size();
+            int bottomGridRowSize = taskCount - mTopRowIdSet.size() - 1;
+            boolean topRowLonger = topGridRowSize > bottomGridRowSize;
+            boolean bottomRowLonger = bottomGridRowSize > topGridRowSize;
+            boolean dismissedTaskFromTop = mTopRowIdSet.contains(dismissedTaskViewId);
+            boolean dismissedTaskFromBottom = !dismissedTaskFromTop && !isFocusedTaskDismissed;
+            float gapWidth = 0;
+            if ((topRowLonger && dismissedTaskFromTop)
+                    || (bottomRowLonger && dismissedTaskFromBottom)) {
+                gapWidth = dismissedTaskWidth;
+            } else if ((topRowLonger && nextFocusedTaskFromTop)
+                    || (bottomRowLonger && !nextFocusedTaskFromTop)) {
+                gapWidth = nextFocusedTaskWidth;
+            }
+            if (gapWidth > 0) {
+                if (taskCount > 2) {
+                    // Compensate the removed gap.
+                    longGridRowWidthDiff += mIsRtl ? -gapWidth : gapWidth;
+                    if (isClearAllHidden) {
+                        // If ClearAllButton isn't fully shown, snap to the last task.
+                        longGridRowWidthDiff += getSnapToLastTaskScrollDiff();
+                    }
+                } else {
+                    // If only focused task will be left, snap to focused task instead.
+                    longGridRowWidthDiff += getSnapToFocusedTaskScrollDiff(isClearAllHidden);
+                }
+            }
+
+            // If we need to animate the grid to compensate the clear all gap, we split the second
+            // half of the dismiss pending animation (in which the non-dismissed tasks slide into
+            // place) in half again, making the first quarter the existing non-dismissal sliding
+            // and the second quarter this new animation of gap filling. This is due to the fact
+            // that PendingAnimation is a single animation, not a sequence of animations, so we
+            // fake it using interpolation.
+            if (longGridRowWidthDiff != 0) {
+                closeGapBetweenClearAll = true;
+                // Stagger the offsets of each additional task for a delayed animation. We use
+                // half here as this animation is half of half of an animation (1/4th).
+                float halfAdditionalDismissTranslationOffset =
+                        (0.5f * ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET);
+                dismissTranslationInterpolationEnd = Utilities.boundToRange(
+                        END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+                                + (taskCount - 1) * halfAdditionalDismissTranslationOffset,
+                        END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1);
+                for (int i = 0; i < taskCount; i++) {
+                    TaskView taskView = getTaskViewAt(i);
+                    anim.setFloat(taskView, TaskView.GRID_END_TRANSLATION_X, longGridRowWidthDiff,
+                            clampToProgress(LINEAR, dismissTranslationInterpolationEnd, 1));
+                    dismissTranslationInterpolationEnd = Utilities.boundToRange(
+                            dismissTranslationInterpolationEnd
+                                    - halfAdditionalDismissTranslationOffset,
+                            END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1);
+                    if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
+                            && taskView.isRunningTask()) {
+                        anim.addOnFrameCallback(() -> {
+                            runActionOnRemoteHandles(
+                                    remoteTargetHandle ->
+                                            remoteTargetHandle.mTaskViewSimulator
+                                                    .taskPrimaryTranslation.value =
+                                                    TaskView.GRID_END_TRANSLATION_X.get(taskView));
+                            redrawLiveTile();
+                        });
+                    }
+                }
+
+                // Change alpha of clear all if translating grid to hide it
+                if (isClearAllHidden) {
+                    anim.setFloat(mClearAllButton, DISMISS_ALPHA, 0, LINEAR);
+                    anim.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            super.onAnimationEnd(animation);
+                            mClearAllButton.setDismissAlpha(1);
+                        }
+                    });
+                }
+            }
+        }
+
         int distanceFromDismissedTask = 0;
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
@@ -2757,22 +2884,25 @@
                 float animationStartProgress = Utilities.boundToRange(
                         INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
                                 + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
-                                * ++distanceFromDismissedTask, 0f, 1f);
+                                * ++distanceFromDismissedTask, 0f,
+                        dismissTranslationInterpolationEnd);
                 if (taskView == nextFocusedTaskView) {
                     // Enlarge the task to be focused next, and translate into focus position.
                     float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width();
                     anim.setFloat(taskView, TaskView.SNAPSHOT_SCALE, scale,
-                            clampToProgress(LINEAR, animationStartProgress, 1f));
+                            clampToProgress(LINEAR, animationStartProgress,
+                                    dismissTranslationInterpolationEnd));
                     anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
                             mIsRtl ? dismissedTaskWidth : -dismissedTaskWidth,
-                            clampToProgress(LINEAR, animationStartProgress, 1f));
+                            clampToProgress(LINEAR, animationStartProgress,
+                                    dismissTranslationInterpolationEnd));
                     float secondaryTranslation = -mTaskGridVerticalDiff;
                     if (!nextFocusedTaskFromTop) {
                         secondaryTranslation -= mTopBottomRowHeightDiff;
                     }
                     anim.setFloat(taskView, taskView.getSecondaryDissmissTranslationProperty(),
-                            secondaryTranslation,
-                            clampToProgress(LINEAR, animationStartProgress, 1f));
+                            secondaryTranslation, clampToProgress(LINEAR, animationStartProgress,
+                                    dismissTranslationInterpolationEnd));
                     anim.setFloat(taskView, TaskView.FOCUS_TRANSITION, 0f,
                             clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT));
                 } else {
@@ -2780,7 +2910,8 @@
                             isFocusedTaskDismissed ? nextFocusedTaskWidth : dismissedTaskWidth;
                     anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
                             mIsRtl ? primaryTranslation : -primaryTranslation,
-                            clampToProgress(LINEAR, animationStartProgress, 1f));
+                            clampToProgress(LINEAR, animationStartProgress,
+                                    dismissTranslationInterpolationEnd));
                 }
             }
         }
@@ -2796,6 +2927,7 @@
 
         mPendingAnimation = anim;
         final TaskView finalNextFocusedTaskView = nextFocusedTaskView;
+        final boolean finalCloseGapBetweenClearAll = closeGapBetweenClearAll;
         mPendingAnimation.addEndListener(new Consumer<Boolean>() {
             @Override
             public void accept(Boolean success) {
@@ -2831,49 +2963,68 @@
                     resetTaskVisuals();
 
                     int pageToSnapTo = mCurrentPage;
+                    mCurrentPageScrollDiff = 0;
                     int taskViewIdToSnapTo = -1;
                     if (showAsGrid) {
-                        // Get the id of the task view we will snap to based on the current
-                        // page's relative position as the order of indices change over time due
-                        // to dismissals.
-                        TaskView snappedTaskView = getTaskViewAtByAbsoluteIndex(mCurrentPage);
-                        if (snappedTaskView != null) {
-                            if (snappedTaskView.getTaskViewId() == mFocusedTaskViewId) {
-                                if (finalNextFocusedTaskView != null) {
-                                    taskViewIdToSnapTo = finalNextFocusedTaskView.getTaskViewId();
-                                } else {
-                                    taskViewIdToSnapTo = mFocusedTaskViewId;
+                        if (finalCloseGapBetweenClearAll) {
+                            if (taskCount > 2) {
+                                pageToSnapTo = indexOfChild(mClearAllButton);
+                                if (isClearAllHidden) {
+                                    int clearAllWidth = mOrientationHandler.getPrimarySize(
+                                            mClearAllButton);
+                                    mCurrentPageScrollDiff =
+                                            isRtl() ? clearAllWidth : -clearAllWidth;
                                 }
-                            } else {
-                                int snappedTaskViewId = snappedTaskView.getTaskViewId();
-                                boolean isSnappedTaskInTopRow = mTopRowIdSet.contains(
-                                        snappedTaskViewId);
-                                IntArray taskViewIdArray =
-                                        isSnappedTaskInTopRow ? getTopRowIdArray()
-                                                : getBottomRowIdArray();
-                                int snappedIndex = taskViewIdArray.indexOf(snappedTaskViewId);
-                                taskViewIdArray.removeValue(dismissedTaskViewId);
-                                if (snappedIndex < taskViewIdArray.size()) {
-                                    taskViewIdToSnapTo = taskViewIdArray.get(snappedIndex);
-                                } else if (snappedIndex == taskViewIdArray.size()) {
-                                    // If the snapped task is the last item from the dismissed row,
-                                    // snap to the same column in the other grid row
-                                    IntArray inverseRowTaskViewIdArray =
-                                            isSnappedTaskInTopRow ? getBottomRowIdArray()
-                                                    : getTopRowIdArray();
-                                    if (snappedIndex < inverseRowTaskViewIdArray.size()) {
-                                        taskViewIdToSnapTo = inverseRowTaskViewIdArray.get(
-                                                snappedIndex);
+                            } else if (isClearAllHidden) {
+                                // Snap to focused task if clear all is hidden.
+                                pageToSnapTo = 0;
+                            }
+                        } else {
+                            // Get the id of the task view we will snap to based on the current
+                            // page's relative position as the order of indices change over time due
+                            // to dismissals.
+                            TaskView snappedTaskView = getTaskViewAtByAbsoluteIndex(mCurrentPage);
+                            if (snappedTaskView != null) {
+                                if (snappedTaskView.getTaskViewId() == mFocusedTaskViewId) {
+                                    if (finalNextFocusedTaskView != null) {
+                                        taskViewIdToSnapTo =
+                                                finalNextFocusedTaskView.getTaskViewId();
+                                    } else {
+                                        taskViewIdToSnapTo = mFocusedTaskViewId;
+                                    }
+                                } else {
+                                    int snappedTaskViewId = snappedTaskView.getTaskViewId();
+                                    boolean isSnappedTaskInTopRow = mTopRowIdSet.contains(
+                                            snappedTaskViewId);
+                                    IntArray taskViewIdArray =
+                                            isSnappedTaskInTopRow ? getTopRowIdArray()
+                                                    : getBottomRowIdArray();
+                                    int snappedIndex = taskViewIdArray.indexOf(snappedTaskViewId);
+                                    taskViewIdArray.removeValue(dismissedTaskViewId);
+                                    if (snappedIndex < taskViewIdArray.size()) {
+                                        taskViewIdToSnapTo = taskViewIdArray.get(snappedIndex);
+                                    } else if (snappedIndex == taskViewIdArray.size()) {
+                                        // If the snapped task is the last item from the
+                                        // dismissed row,
+                                        // snap to the same column in the other grid row
+                                        IntArray inverseRowTaskViewIdArray =
+                                                isSnappedTaskInTopRow ? getBottomRowIdArray()
+                                                        : getTopRowIdArray();
+                                        if (snappedIndex < inverseRowTaskViewIdArray.size()) {
+                                            taskViewIdToSnapTo = inverseRowTaskViewIdArray.get(
+                                                    snappedIndex);
+                                        }
                                     }
                                 }
                             }
-                        }
 
-                        int primaryScroll = mOrientationHandler.getPrimaryScroll(RecentsView.this);
-                        int currentPageScroll = getScrollForPage(pageToSnapTo);
-                        mCurrentPageScrollDiff = primaryScroll - currentPageScroll;
+                            int primaryScroll = mOrientationHandler.getPrimaryScroll(
+                                    RecentsView.this);
+                            int currentPageScroll = getScrollForPage(pageToSnapTo);
+                            mCurrentPageScrollDiff = primaryScroll - currentPageScroll;
+                        }
                     } else if (dismissedIndex < pageToSnapTo || pageToSnapTo == taskCount - 1) {
-                        pageToSnapTo -= 1;
+                        pageToSnapTo--;
                     }
                     removeViewInLayout(dismissedTaskView);
                     mTopRowIdSet.remove(dismissedTaskViewId);
@@ -2938,7 +3089,13 @@
                             }
                         }
                         setCurrentPage(pageToSnapTo);
+                        // Update various scroll depedent UI.
                         dispatchScrollChanged();
+                        updateActionsViewScrollAlpha();
+                        if (isClearAllHidden()) {
+                            mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING,
+                                    false);
+                        }
                     }
                 }
                 updateFocusedSplitButtonVisibility();
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 80f5dc4..8a20da6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -343,6 +343,19 @@
                 }
             };
 
+    public static final FloatProperty<TaskView> GRID_END_TRANSLATION_X =
+            new FloatProperty<TaskView>("gridEndTranslationX") {
+                @Override
+                public void setValue(TaskView taskView, float v) {
+                    taskView.setGridEndTranslationX(v);
+                }
+
+                @Override
+                public Float get(TaskView taskView) {
+                    return taskView.mGridEndTranslationX;
+                }
+            };
+
     public static final FloatProperty<TaskView> SNAPSHOT_SCALE =
             new FloatProperty<TaskView>("snapshotScale") {
                 @Override
@@ -381,6 +394,8 @@
     // The following grid translations scales with mGridProgress.
     private float mGridTranslationX;
     private float mGridTranslationY;
+    // The following grid translation is used to animate closing the gap between grid and clear all.
+    private float mGridEndTranslationX;
     // Applied as a complement to gridTranslation, for adjusting the carousel overview and quick
     // switch.
     private float mNonGridTranslationX;
@@ -950,8 +965,8 @@
     protected void resetViewTransforms() {
         // fullscreenTranslation and accumulatedTranslation should not be reset, as
         // resetViewTransforms is called during Quickswitch scrolling.
-        mDismissTranslationX = mTaskOffsetTranslationX = mTaskResistanceTranslationX =
-                mSplitSelectTranslationX = 0f;
+        mDismissTranslationX = mTaskOffsetTranslationX =
+                mTaskResistanceTranslationX = mSplitSelectTranslationX = mGridEndTranslationX = 0f;
         mDismissTranslationY = mTaskOffsetTranslationY = mTaskResistanceTranslationY =
                 mSplitSelectTranslationY = 0f;
         setSnapshotScale(1f);
@@ -1162,6 +1177,11 @@
         return mGridTranslationY;
     }
 
+    private void setGridEndTranslationX(float gridEndTranslationX) {
+        mGridEndTranslationX = gridEndTranslationX;
+        applyTranslationX();
+    }
+
     public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
         float scrollAdjustment = 0;
         if (gridEnabled) {
@@ -1191,7 +1211,7 @@
 
     private void applyTranslationX() {
         setTranslationX(mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX
-                + mSplitSelectTranslationX + getPersistentTranslationX());
+                + mSplitSelectTranslationX + mGridEndTranslationX + getPersistentTranslationX());
     }
 
     private void applyTranslationY() {