[automerger] Various quickscrub fixes am: 9bb8ffb5e3

Change-Id: Id9f1cb83795b3164dd8bc67c879a3b3d8b8fd91f
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index e202c57..52a6dd5 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherInitListener;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -82,7 +83,8 @@
     void onQuickInteractionStart(T activity, @Nullable RunningTaskInfo taskInfo,
             boolean activityVisible);
 
-    float getTranslationYForQuickScrub(T activity);
+    float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp,
+            Context context);
 
     void executeOnWindowAvailable(T activity, Runnable action);
 
@@ -151,10 +153,15 @@
         }
 
         @Override
-        public float getTranslationYForQuickScrub(Launcher activity) {
-            LauncherRecentsView recentsView = activity.getOverviewPanel();
-            return recentsView.computeTranslationYForFactor(
-                    FastOverviewState.OVERVIEW_TRANSLATION_FACTOR);
+        public float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp,
+                Context context) {
+            // The padding calculations are exactly same as that of RecentsView.setInsets
+            int topMargin = context.getResources()
+                    .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+            int paddingTop = targetRect.rect.top - topMargin - dp.getInsets().top;
+            int paddingBottom = dp.availableHeightPx + dp.getInsets().top - targetRect.rect.bottom;
+
+            return FastOverviewState.OVERVIEW_TRANSLATION_FACTOR * (paddingBottom - paddingTop);
         }
 
         @Override
@@ -380,7 +387,8 @@
         }
 
         @Override
-        public float getTranslationYForQuickScrub(RecentsActivity activity) {
+        public float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp,
+                Context context) {
             return 0;
         }
 
diff --git a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
index b92678a..8e83bd0 100644
--- a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
@@ -64,6 +64,16 @@
     }
 
     @Override
+    public void onQuickStep(MotionEvent ev) {
+        mTarget.onQuickStep(ev);
+    }
+
+    @Override
+    public void onCommand(int command) {
+        mTarget.onCommand(command);
+    }
+
+    @Override
     public void preProcessMotionEvent(MotionEvent ev) {
         mVelocityTracker.addMovement(ev);
     }
@@ -92,6 +102,11 @@
         return target == null ? true : target.deferNextEventToMainThread();
     }
 
+    @Override
+    public void onShowOverviewFromAltTab() {
+        mTarget.onShowOverviewFromAltTab();
+    }
+
     public interface DeferredTouchProvider {
 
         TouchConsumer createTouchConsumer(VelocityTracker tracker);
diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java
index 15f5aa5..f73be6c 100644
--- a/quickstep/src/com/android/quickstep/MotionEventQueue.java
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -55,6 +55,8 @@
             ACTION_VIRTUAL | (6 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_QUICK_STEP =
             ACTION_VIRTUAL | (7 << ACTION_POINTER_INDEX_SHIFT);
+    private static final int ACTION_COMMAND =
+            ACTION_VIRTUAL | (8 << ACTION_POINTER_INDEX_SHIFT);
 
     private final EventArray mEmptyArray = new EventArray();
     private final Object mExecutionLock = new Object();
@@ -165,6 +167,9 @@
                         case ACTION_QUICK_STEP:
                             mConsumer.onQuickStep(event);
                             break;
+                        case ACTION_COMMAND:
+                            mConsumer.onCommand(event.getSource());
+                            break;
                         default:
                             Log.e(TAG, "Invalid virtual event: " + event.getAction());
                     }
@@ -222,6 +227,12 @@
         queueVirtualAction(ACTION_DEFER_INIT, 0);
     }
 
+    public void onCommand(int command) {
+        MotionEvent ev = MotionEvent.obtain(0, 0, ACTION_COMMAND, 0, 0, 0);
+        ev.setSource(command);
+        queueNoPreProcess(ev);
+    }
+
     public TouchConsumer getConsumer() {
         return mConsumer;
     }
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 9ba3328..23357ea 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -37,6 +37,7 @@
 import android.os.Bundle;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.util.SparseArray;
 import android.view.Choreographer;
 import android.view.Display;
 import android.view.MotionEvent;
@@ -69,6 +70,7 @@
 
     private static final long LAUNCHER_DRAW_TIMEOUT_MS = 150;
 
+    private final SparseArray<RecentsAnimationState> mAnimationStates = new SparseArray<>();
     private final RunningTaskInfo mRunningTask;
     private final RecentsModel mRecentsModel;
     private final Intent mHomeIntent;
@@ -212,8 +214,9 @@
 
     private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
         // Create the shared handler
+        RecentsAnimationState animationState = new RecentsAnimationState();
         final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
-                mRunningTask, this, touchTimeMs, mActivityControlHelper);
+                animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper);
 
         // Preload the plan
         mRecentsModel.loadTasks(mRunningTask.id, null);
@@ -237,31 +240,7 @@
                     public void onHandleAssistData(Bundle bundle) {
                         mRecentsModel.preloadAssistData(mRunningTask.id, bundle);
                     }
-                },
-                new RecentsAnimationListener() {
-                    public void onAnimationStart(
-                            RecentsAnimationControllerCompat controller,
-                            RemoteAnimationTargetCompat[] apps, Rect homeContentInsets,
-                            Rect minimizedHomeBounds) {
-                        if (mInteractionHandler == handler) {
-                            TraceHelper.partitionSection("RecentsController", "Received");
-                            handler.onRecentsAnimationStart(controller,
-                                    new RemoteAnimationTargetSet(apps, MODE_CLOSING),
-                                    homeContentInsets, minimizedHomeBounds);
-                        } else {
-                            TraceHelper.endSection("RecentsController", "Finishing no handler");
-                            controller.finish(false /* toHome */);
-                        }
-                    }
-
-                    public void onAnimationCanceled() {
-                        TraceHelper.endSection("RecentsController",
-                                "Cancelled: " + mInteractionHandler);
-                        if (mInteractionHandler == handler) {
-                            handler.onRecentsAnimationCanceled();
-                        }
-                    }
-                }, null, null);
+                }, animationState, null, null);
 
         if (Looper.myLooper() != Looper.getMainLooper()) {
             startActivity.run();
@@ -277,6 +256,14 @@
         }
     }
 
+    @Override
+    public void onCommand(int command) {
+        RecentsAnimationState state = mAnimationStates.get(command);
+        if (state != null) {
+            state.execute();
+        }
+    }
+
     /**
      * Called when the gesture has ended. Does not correlate to the completion of the interaction as
      * the animation can still be running.
@@ -398,4 +385,55 @@
         // TODO: Consider also check if the eventQueue is using mainThread of not.
         return mInteractionHandler != null;
     }
+
+    private class RecentsAnimationState implements RecentsAnimationListener {
+
+        private final int id;
+
+        private RecentsAnimationControllerCompat mController;
+        private RemoteAnimationTargetSet mTargets;
+        private Rect mHomeContentInsets;
+        private Rect mMinimizedHomeBounds;
+        private boolean mCancelled;
+
+        public RecentsAnimationState() {
+            id = mAnimationStates.size();
+            mAnimationStates.put(id, this);
+        }
+
+        @Override
+        public void onAnimationStart(
+                RecentsAnimationControllerCompat controller,
+                RemoteAnimationTargetCompat[] apps, Rect homeContentInsets,
+                Rect minimizedHomeBounds) {
+            mController = controller;
+            mTargets = new RemoteAnimationTargetSet(apps, MODE_CLOSING);
+            mHomeContentInsets = homeContentInsets;
+            mMinimizedHomeBounds = minimizedHomeBounds;
+            mEventQueue.onCommand(id);
+        }
+
+        @Override
+        public void onAnimationCanceled() {
+            mCancelled = true;
+            mEventQueue.onCommand(id);
+        }
+
+        public void execute() {
+            if (mInteractionHandler == null || mInteractionHandler.id != id) {
+                if (!mCancelled && mController != null) {
+                    TraceHelper.endSection("RecentsController", "Finishing no handler");
+                    mController.finish(false /* toHome */);
+                }
+            } else if (mCancelled) {
+                TraceHelper.endSection("RecentsController",
+                        "Cancelled: " + mInteractionHandler);
+                mInteractionHandler.onRecentsAnimationCanceled();
+            } else {
+                TraceHelper.partitionSection("RecentsController", "Received");
+                mInteractionHandler.onRecentsAnimationStart(mController, mTargets,
+                        mHomeContentInsets, mMinimizedHomeBounds);
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
index 30b10b0..34d42ac 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -20,6 +20,8 @@
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+
+import java.util.ArrayList;
 import java.util.concurrent.ExecutorService;
 
 /**
@@ -27,6 +29,10 @@
  */
 public class RecentsAnimationWrapper {
 
+    // A list of callbacks to run when we receive the recents animation target. There are different
+    // than the state callbacks as these run on the current worker thread.
+    private final ArrayList<Runnable> mCallbacks = new ArrayList<>();
+
     public RemoteAnimationTargetSet targetSet;
 
     private RecentsAnimationControllerCompat mController;
@@ -46,6 +52,21 @@
         if (mInputConsumerEnabled) {
             enableInputConsumer();
         }
+
+        if (!mCallbacks.isEmpty()) {
+            for (Runnable action : new ArrayList<>(mCallbacks)) {
+                action.run();
+            }
+            mCallbacks.clear();
+        }
+    }
+
+    public synchronized void runOnInit(Runnable action) {
+        if (targetSet == null) {
+            mCallbacks.add(action);
+        } else {
+            action.run();
+        }
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java
index aa844d8..4cecffa 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -48,6 +48,8 @@
 
     default void onQuickStep(MotionEvent ev) { }
 
+    default void onCommand(int command) { }
+
     /**
      * Called on the binder thread to allow the consumer to process the motion event before it is
      * posted on a handler thread.
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index d171f69..939811b 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -29,6 +29,7 @@
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -36,6 +37,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.support.annotation.AnyThread;
 import android.support.annotation.UiThread;
 import android.support.annotation.WorkerThread;
@@ -51,6 +53,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.logging.UserEventDispatcher;
@@ -65,11 +68,12 @@
 import com.android.quickstep.ActivityControlHelper.LayoutListener;
 import com.android.quickstep.TouchConsumer.InteractionType;
 import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.TransformedRect;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.TransformedRect;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -167,6 +171,12 @@
 
     private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
 
+    // An increasing identifier per single instance of OtherActivityTouchConsumer. Generally one
+    // instance of OtherActivityTouchConsumer will only have one swipe handle, but sometimes we can
+    // end up with multiple handlers if we get recents command in the middle of a swipe gesture.
+    // This is used to match the corresponding activity manager callbacks in
+    // OtherActivityTouchConsumer
+    public final int id;
     private final Context mContext;
     private final ActivityControlHelper<T> mActivityControlHelper;
     private final ActivityInitListener mActivityInitListener;
@@ -199,6 +209,7 @@
             InputConsumerController.getRecentsAnimationInputConsumer();
 
     private final RecentsAnimationWrapper mRecentsAnimationWrapper = new RecentsAnimationWrapper();
+
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
 
@@ -207,8 +218,9 @@
     private float mLongSwipeDisplacement = 0;
     private LongSwipeHelper mLongSwipeController;
 
-    WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs,
-            ActivityControlHelper<T> controller) {
+    WindowTransformSwipeHandler(int id, RunningTaskInfo runningTaskInfo, Context context,
+            long touchTimeMs, ActivityControlHelper<T> controller) {
+        this.id = id;
         mContext = context;
         mRunningTaskInfo = runningTaskInfo;
         mRunningTaskId = runningTaskInfo.id;
@@ -453,6 +465,7 @@
                     "Can't change interaction type to " + interactionType);
         }
         mInteractionType = interactionType;
+        mRecentsAnimationWrapper.runOnInit(this::shiftAnimationDestinationForQuickscrub);
 
         setStateOnUiThread(STATE_QUICK_SCRUB_START | STATE_GESTURE_COMPLETED);
 
@@ -460,6 +473,34 @@
         animateToProgress(1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR);
     }
 
+    private void shiftAnimationDestinationForQuickscrub() {
+        TransformedRect tempRect = new TransformedRect();
+        mActivityControlHelper
+                .getSwipeUpDestinationAndLength(mDp, mContext, mInteractionType, tempRect);
+        mClipAnimationHelper.updateTargetRect(tempRect);
+
+        float offsetY =
+                mActivityControlHelper.getTranslationYForQuickScrub(tempRect, mDp, mContext);
+        float scale, offsetX;
+        Resources res = mContext.getResources();
+
+        if (ActivityManagerWrapper.getInstance().getRecentTasks(2, UserHandle.myUserId()).size()
+                < 2) {
+            // There are not enough tasks, we don't need to shift
+            offsetX = 0;
+            scale = 1;
+        } else {
+            offsetX = res.getDimensionPixelSize(R.dimen.recents_page_spacing)
+                    + tempRect.rect.width();
+            float distanceToReachEdge = mDp.widthPx / 2 + tempRect.rect.width() / 2 +
+                    res.getDimensionPixelSize(R.dimen.recents_page_spacing);
+            float interpolation = Math.min(1, offsetX / distanceToReachEdge);
+            scale = TaskView.getCurveScaleForInterpolation(interpolation);
+        }
+        mClipAnimationHelper.offsetTarget(scale, Utilities.isRtl(res) ? -offsetX : offsetX, offsetY,
+                QuickScrubController.QUICK_SCRUB_START_INTERPOLATOR);
+    }
+
     @WorkerThread
     public void updateDisplacement(float displacement) {
         // We are moving in the negative x/y direction
@@ -658,7 +699,7 @@
                         : STATE_SCALED_CONTROLLER_APP);
             }
         });
-        anim.start();
+        mRecentsAnimationWrapper.runOnInit(anim::start);
     }
 
     @UiThread
@@ -786,30 +827,6 @@
 
         // Inform the last progress in case we skipped before.
         mQuickScrubController.onQuickScrubProgress(mCurrentQuickScrubProgress);
-
-        // Make sure the window follows the first task if it moves, e.g. during quick scrub.
-        TaskView firstTask = mRecentsView.getPageAt(0);
-        // The first task may be null if we are swiping up from a task that does not
-        // appear in the list (i.e. the assistant)
-        if (firstTask != null) {
-            int scrollForFirstTask = mRecentsView.getScrollForPage(0);
-            int scrollForSecondTask = mRecentsView.getChildCount() > 1
-                    ? mRecentsView.getScrollForPage(1) : scrollForFirstTask;
-            float offsetFromFirstTask = scrollForFirstTask - scrollForSecondTask;
-
-            TransformedRect tempRect = new TransformedRect();
-            mActivityControlHelper
-                    .getSwipeUpDestinationAndLength(mDp, mContext, mInteractionType, tempRect);
-            float distanceToReachEdge = mDp.widthPx / 2 + tempRect.rect.width() / 2 +
-                    mContext.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
-            float interpolation = Math.min(1,
-                    Math.abs(offsetFromFirstTask) / distanceToReachEdge);
-
-            mClipAnimationHelper.offsetTarget(
-                    firstTask.getCurveScaleForInterpolation(interpolation), offsetFromFirstTask,
-                    mActivityControlHelper.getTranslationYForQuickScrub(mActivity),
-                    QuickScrubController.QUICK_SCRUB_START_INTERPOLATOR);
-        }
     }
 
     private void onFinishedTransitionToQuickScrub() {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 02cdd3a..776509c 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -507,6 +507,7 @@
         DeviceProfile dp = mActivity.getDeviceProfile();
         getTaskSize(dp, mTempRect);
 
+        // Keep this logic in sync with ActivityControlHelper.getTranslationYForQuickScrub.
         mTempRect.top -= mTaskTopMargin;
         setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
                 dp.availableWidthPx + mInsets.left - mTempRect.right,
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 5413a13..91c0fa5 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -250,12 +250,12 @@
         setPivotY(mSnapshotView.getTop() + mSnapshotView.getHeight() * 0.5f);
     }
 
-    public float getCurveScaleForInterpolation(float linearInterpolation) {
+    public static float getCurveScaleForInterpolation(float linearInterpolation) {
         float curveInterpolation = CURVE_INTERPOLATOR.getInterpolation(linearInterpolation);
         return getCurveScaleForCurveInterpolation(curveInterpolation);
     }
 
-    private float getCurveScaleForCurveInterpolation(float curveInterpolation) {
+    private static float getCurveScaleForCurveInterpolation(float curveInterpolation) {
         return 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
     }
 
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 62b581f..de9cd98 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1450,6 +1450,10 @@
 
     protected boolean snapToPage(int whichPage, int delta, int duration, boolean immediate,
             TimeInterpolator interpolator) {
+        if (mFirstLayout) {
+            setCurrentPage(whichPage);
+            return false;
+        }
 
         if (FeatureFlags.IS_DOGFOOD_BUILD) {
             duration *= Settings.System.getFloat(getContext().getContentResolver(),