Merge "Ditch work profile orange and use the accent color instead" into ub-launcher3-master
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index bdc7c36..d8504f1 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -29,7 +29,7 @@
     <dimen name="quickstep_fling_threshold_velocity">500dp</dimen>
     <dimen name="quickstep_fling_min_velocity">250dp</dimen>
 
-    <dimen name="workspace_overview_offset_x">-30dp</dimen>
+    <dimen name="workspace_overview_offset_x">-24dp</dimen>
 
     <!-- TODO: This can be calculated using other resource values -->
     <dimen name="all_apps_search_box_full_height">90dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 7433dfd..31c195d 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -421,8 +421,9 @@
                                          Runnable finishedCallback) {
                 Handler handler = mLauncher.getWindow().getDecorView().getHandler();
                 postAtFrontOfQueueAsynchronously(handler, () -> {
-                    // We use a separate transition for Overview mode.
-                    if (mLauncher.isInState(LauncherState.OVERVIEW)) {
+                    if (Utilities.getPrefs(mLauncher).getBoolean("pref_use_screenshot_animation",
+                            true) && mLauncher.isInState(LauncherState.OVERVIEW)) {
+                        // We use a separate transition for Overview mode.
                         setCurrentAnimator(null);
                         finishedCallback.run();
                         return;
@@ -470,14 +471,14 @@
 
                 float dX = getValue(0, endX, 0, 350, currentPlayTime,
                         Interpolators.AGGRESSIVE_EASE_IN_OUT);
+                float dY = (height - height * scale) / 2f;
 
                 TransactionCompat t = new TransactionCompat();
                 for (RemoteAnimationTargetCompat app : targets) {
                     if (app.mode == RemoteAnimationTargetCompat.MODE_CLOSING) {
                         t.setAlpha(app.leash, 1f - percent);
-
-                        float dY = (height - (app.clipRect.height() * scale)) / 2f;
                         matrix.postTranslate(dX, dY);
+                        matrix.postTranslate(app.position.x, app.position.y);
                         t.setMatrix(app.leash, matrix);
                     }
                     // TODO: Layer should be set only once, but there is possibly a race condition
diff --git a/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
index 92a09dd..8533502 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
@@ -76,7 +76,7 @@
         mTranslateXPage0 = scale[1];
         mTranslateXPage1 = OverviewState
                 .getScaleAndTranslationForPageRect(mLauncher,
-                        getResources().getDimension(R.dimen.workspace_overview_offset_x),
+                        getResources().getDimension(R.dimen.workspace_overview_offset_x) / scale[0],
                         mTempRect)[1];
 
         mExtraScrollShift = 0;
diff --git a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
index 0551938..aa210b8 100644
--- a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep;
 
+import android.support.annotation.WorkerThread;
+
 import com.android.launcher3.states.InternalStateHandler;
 import com.android.quickstep.TouchInteractionService.InteractionType;
 
@@ -28,8 +30,10 @@
 
     public void reset() {}
 
+    @WorkerThread
     public abstract void onGestureStarted();
 
+    @WorkerThread
     public abstract void onGestureEnded(float endVelocity);
 
     public abstract void updateInteractionType(@InteractionType int interactionType);
@@ -38,5 +42,6 @@
 
     public abstract void onQuickScrubProgress(float progress);
 
+    @WorkerThread
     public abstract void updateDisplacement(float displacement);
 }
diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java
index e3c3a1b..fae9b66 100644
--- a/quickstep/src/com/android/quickstep/MotionEventQueue.java
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -18,6 +18,8 @@
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_MOVE;
 
+import android.annotation.TargetApi;
+import android.os.Build;
 import android.view.Choreographer;
 import android.view.MotionEvent;
 
@@ -29,25 +31,64 @@
 /**
  * Helper class for batching input events
  */
-public class MotionEventQueue implements Runnable {
+@TargetApi(Build.VERSION_CODES.O)
+public class MotionEventQueue {
+
+    private final EventArray mEmptyArray = new EventArray();
+    private final Object mExecutionLock = new Object();
 
     // We use two arrays and swap the current index when one array is being consumed
     private final EventArray[] mArrays = new EventArray[] {new EventArray(), new EventArray()};
     private int mCurrentIndex = 0;
 
-    private final Choreographer mChoreographer;
-    private final Consumer<MotionEvent> mConsumer;
+    private final Runnable mMainFrameCallback = this::frameCallbackForMainChoreographer;
+    private final Runnable mInterimFrameCallback = this::frameCallbackForInterimChoreographer;
+
+    private final Choreographer mMainChoreographer;
+
+    private Consumer<MotionEvent> mConsumer;
+
+    private Choreographer mInterimChoreographer;
+    private Choreographer mCurrentChoreographer;
+
+    private Runnable mCurrentRunnable;
 
     public MotionEventQueue(Choreographer choreographer, Consumer<MotionEvent> consumer) {
-        mChoreographer = choreographer;
+        mMainChoreographer = choreographer;
         mConsumer = consumer;
+
+        mCurrentChoreographer = mMainChoreographer;
+        mCurrentRunnable = mMainFrameCallback;
+    }
+
+    public void setConsumer(Consumer<MotionEvent> consumer) {
+        synchronized (mExecutionLock) {
+            mConsumer = consumer;
+        }
+    }
+
+    public void setInterimChoreographer(Choreographer choreographer) {
+        synchronized (mExecutionLock) {
+            synchronized (mArrays) {
+                mInterimChoreographer = choreographer;
+                if (choreographer == null) {
+                    mCurrentChoreographer = mMainChoreographer;
+                    mCurrentRunnable = mMainFrameCallback;
+                } else {
+                    mCurrentChoreographer = mInterimChoreographer;
+                    mCurrentRunnable = mInterimFrameCallback;
+                }
+
+                ChoreographerCompat.postInputFrame(mCurrentChoreographer, mCurrentRunnable);
+            }
+        }
     }
 
     public void queue(MotionEvent event) {
         synchronized (mArrays) {
             EventArray array = mArrays[mCurrentIndex];
             if (array.isEmpty()) {
-                ChoreographerCompat.postInputFrame(mChoreographer, this);
+                ChoreographerCompat.postInputFrame(mCurrentChoreographer, mCurrentRunnable);
             }
 
             int eventAction = event.getAction();
@@ -61,21 +102,33 @@
         }
     }
 
-    @Override
-    public void run() {
-        EventArray array = swapAndGetCurrentArray();
-        int size = array.size();
-        for (int i = 0; i < size; i++) {
-            MotionEvent event = array.get(i);
-            mConsumer.accept(event);
-            event.recycle();
-        }
-        array.clear();
-        array.lastEventAction = ACTION_CANCEL;
+    private void frameCallbackForMainChoreographer() {
+        runFor(mMainChoreographer);
     }
 
-    private EventArray swapAndGetCurrentArray() {
+    private void frameCallbackForInterimChoreographer() {
+        runFor(mInterimChoreographer);
+    }
+
+    private void runFor(Choreographer caller) {
+        synchronized (mExecutionLock) {
+            EventArray array = swapAndGetCurrentArray(caller);
+            int size = array.size();
+            for (int i = 0; i < size; i++) {
+                MotionEvent event = array.get(i);
+                mConsumer.accept(event);
+                event.recycle();
+            }
+            array.clear();
+            array.lastEventAction = ACTION_CANCEL;
+        }
+    }
+
+    private EventArray swapAndGetCurrentArray(Choreographer caller) {
         synchronized (mArrays) {
+            if (caller != mCurrentChoreographer) {
+                return mEmptyArray;
+            }
             EventArray current = mArrays[mCurrentIndex];
             mCurrentIndex = mCurrentIndex ^ 1;
             return current;
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
index 944804b..e2abd59 100644
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -92,7 +92,7 @@
     // animated to 1, so allow for a smooth transition.
     private final AnimatedFloat mActivityMultiplier = new AnimatedFloat(this::updateFinalShift);
 
-    private final Task mRunningTask;
+    private final int mRunningTaskId;
     private final Context mContext;
 
     private final MultiStateCallback mStateCallback;
@@ -116,13 +116,9 @@
 
     NavBarSwipeInteractionHandler(RunningTaskInfo runningTaskInfo, Context context,
             @InteractionType int interactionType) {
-        // TODO: We need a better way for this
-        TaskKey taskKey = new TaskKey(runningTaskInfo.id, 0, null, UserHandle.myUserId(), 0);
-        mRunningTask = new Task(taskKey, null, null, "", "", Color.BLACK, Color.BLACK,
-                true, false, false, false, null, 0, null, false);
-
         mContext = context;
         mInteractionType = interactionType;
+        mRunningTaskId = runningTaskInfo.id;
         WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
 
         DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext);
@@ -192,7 +188,7 @@
         launcher.setOnResumeCallback(this);
         mLauncher = launcher;
         mRecentsView = launcher.getOverviewPanel();
-        mRecentsView.showTask(mRunningTask);
+        mRecentsView.showTask(mRunningTaskId);
         mHotseat = mLauncher.getHotseat();
         mWasLauncherAlreadyVisible = alreadyOnHome;
 
@@ -340,18 +336,15 @@
 
     @UiThread
     private void resumeLastTask() {
-        // TODO: We need a better way for this
-        TaskKey key = mRunningTask.key;
         RecentsTaskLoadPlan loadPlan = RecentsModel.getInstance(mContext).getLastLoadPlan();
         if (loadPlan != null) {
-            Task task = loadPlan.getTaskStack().findTaskWithId(key.id);
+            Task task = loadPlan.getTaskStack().findTaskWithId(mRunningTaskId);
             if (task != null) {
-                key = task.key;
+                ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
+                ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(task.key, opts,
+                        null, null);
             }
         }
-
-        ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
-        ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(key, opts, null, null);
     }
 
     public void reset() {
@@ -380,8 +373,7 @@
         if (mInteractionType == INTERACTION_QUICK_SWITCH) {
             for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
                 TaskView taskView = (TaskView) mRecentsView.getPageAt(i);
-                // TODO: Match the keys directly
-                if (taskView.getTask().key.id != mRunningTask.key.id) {
+                if (taskView.getTask().key.id != mRunningTaskId) {
                     mRecentsView.snapToPage(i, QUICK_SWITCH_SNAP_DURATION);
                     taskView.postDelayed(() -> {taskView.launchTask(true);},
                             QUICK_SWITCH_SNAP_DURATION);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
new file mode 100644
index 0000000..7c98317
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * Wrapper around RecentsAnimationController to help with some synchronization
+ */
+public class RecentsAnimationWrapper {
+
+    public RecentsAnimationControllerCompat controller;
+    public RemoteAnimationTargetCompat[] targets;
+
+    private boolean mInputConsumerEnabled;
+
+    public synchronized void setController(
+            RecentsAnimationControllerCompat controller, RemoteAnimationTargetCompat[] targets) {
+        this.controller = controller;
+        this.targets = targets;
+
+        if (mInputConsumerEnabled) {
+            enableInputConsumer();
+        }
+    }
+
+    public void finish(boolean toHome) {
+        BackgroundExecutor.get().submit(() -> {
+            synchronized (this) {
+                if (controller != null) {
+                    controller.setInputConsumerEnabled(false);
+                    controller.finish(toHome);
+                }
+            }
+        });
+    }
+
+    public void enableInputConsumer() {
+        mInputConsumerEnabled = true;
+        if (mInputConsumerEnabled) {
+            BackgroundExecutor.get().submit(() -> {
+                synchronized (this) {
+                    if (controller != null) {
+                        controller.setInputConsumerEnabled(true);
+                    }
+                }
+            });
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
index 6937c1f..8e03f37 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -98,7 +98,8 @@
     private final RecentsModel mModel;
     private int mLoadPlanId = -1;
 
-    private Task mFirstTask;
+    // Only valid until the launcher state changes to NORMAL
+    private int mRunningTaskId = -1;
 
     private Bitmap mScrim;
     private Paint mFadePaint;
@@ -241,13 +242,6 @@
         final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
         setLayoutTransition(null);
 
-        if (mFirstTask != null) {
-            // TODO: Handle this case here once we have a valid implementation for mFirstTask
-            if (tasks.isEmpty() || !keysEquals(tasks.get(tasks.size() - 1), mFirstTask)) {
-                // tasks.add(mFirstTask);
-            }
-        }
-
         final int requiredChildCount = tasks.size() + mFirstTaskIndex;
         for (int i = getChildCount(); i < requiredChildCount; i++) {
             final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
@@ -394,7 +388,7 @@
     }
 
     public void reset() {
-        mFirstTask = null;
+        mRunningTaskId = -1;
         setCurrentPage(0);
     }
 
@@ -407,11 +401,7 @@
      */
     public void reloadIfNeeded() {
         if (!mModel.isLoadPlanValid(mLoadPlanId)) {
-            int taskId = -1;
-            if (mFirstTask != null) {
-                taskId = mFirstTask.key.id;
-            }
-            mLoadPlanId = mModel.loadTasks(taskId, this::applyLoadPlan);
+            mLoadPlanId = mModel.loadTasks(mRunningTaskId, this::applyLoadPlan);
         }
     }
 
@@ -423,41 +413,30 @@
      * is called.
      * Also scrolls the view to this task
      */
-    public void showTask(Task task) {
+    public void showTask(int runningTaskId) {
         boolean needsReload = false;
-        boolean inflateFirstChild = true;
-        if (getTaskCount() > 0) {
-            TaskView tv = (TaskView) getChildAt(mFirstTaskIndex);
-            inflateFirstChild = !keysEquals(tv.getTask(), task);
-        }
-        if (inflateFirstChild) {
+        if (getTaskCount() == 0) {
             needsReload = true;
-            setLayoutTransition(null);
             // Add an empty view for now
+            setLayoutTransition(null);
             final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
                     .inflate(R.layout.task, this, false);
             addView(taskView, mFirstTaskIndex);
-            taskView.bind(task);
             setLayoutTransition(mLayoutTransition);
         }
         if (!needsReload) {
             needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
         }
         if (needsReload) {
-            mLoadPlanId = mModel.loadTasks(task.key.id, this::applyLoadPlan);
+            mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
         }
-        mFirstTask = task;
+        mRunningTaskId = runningTaskId;
         setCurrentPage(mFirstTaskIndex);
         if (mCurrentPage >= mFirstTaskIndex) {
             ((TaskView) getPageAt(mCurrentPage)).setIconScale(0);
         }
     }
 
-    private static boolean keysEquals(Task t1, Task t2) {
-        // TODO: Match the keys directly
-        return t1.key.id == t2.key.id;
-    }
-
     public QuickScrubController getQuickScrubController() {
         return mQuickScrubController;
     }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index c0b12f7..0490832 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -40,6 +40,8 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.support.annotation.IntDef;
 import android.util.Log;
@@ -56,6 +58,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.model.ModelPreload;
 import com.android.launcher3.util.TraceHelper;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -92,11 +95,16 @@
     public static final int INTERACTION_QUICK_SWITCH = 1;
     public static final int INTERACTION_QUICK_SCRUB = 2;
 
+    /**
+     * A background thread used for handling UI for another window.
+     */
+    private static HandlerThread sRemoteUiThread;
+
     private final IBinder mMyBinder = new IOverviewProxy.Stub() {
 
         @Override
         public void onMotionEvent(MotionEvent ev) {
-            mEventQueue.queue(ev);
+            onBinderMotionEvent(ev);
         }
 
         @Override
@@ -166,7 +174,8 @@
     private Rect mStableInsets = new Rect();
 
     private ISystemUiProxy mISystemUiProxy;
-    private Consumer<MotionEvent> mCurrentConsumer = mNoOpTouchConsumer;
+
+    private Choreographer mBackgroundThreadChoreographer;
 
     @Override
     public void onCreate() {
@@ -184,8 +193,11 @@
         // Clear the packageName as system can fail to dedupe it b/64108432
         mHomeIntent.setComponent(mLauncher).setPackage(null);
 
-        mEventQueue = new MotionEventQueue(Choreographer.getInstance(), this::handleMotionEvent);
+        mEventQueue = new MotionEventQueue(Choreographer.getInstance(), mNoOpTouchConsumer);
         sConnected = true;
+
+        new ModelPreload().start(this);
+        initBackgroundChoreographer();
     }
 
     @Override
@@ -201,19 +213,23 @@
         return mMyBinder;
     }
 
-    private void handleMotionEvent(MotionEvent ev) {
+    private void onBinderMotionEvent(MotionEvent ev) {
         if (ev.getActionMasked() == ACTION_DOWN) {
             mRunningTask = mAM.getRunningTask();
 
             if (mRunningTask == null) {
-                mCurrentConsumer = mNoOpTouchConsumer;
+                mEventQueue.setConsumer(mNoOpTouchConsumer);
+                mEventQueue.setInterimChoreographer(null);
             } else if (mRunningTask.topActivity.equals(mLauncher)) {
-                mCurrentConsumer = getLauncherConsumer();
+                mEventQueue.setConsumer(getLauncherConsumer());
+                mEventQueue.setInterimChoreographer(null);
             } else {
-                mCurrentConsumer = mOtherActivityTouchConsumer;
+                mEventQueue.setConsumer(mOtherActivityTouchConsumer);
+                mEventQueue.setInterimChoreographer(
+                        isUsingScreenShot() ? null : mBackgroundThreadChoreographer);
             }
         }
-        mCurrentConsumer.accept(ev);
+        mEventQueue.queue(ev);
     }
 
     private void handleTouchDownOnOtherActivity(MotionEvent ev) {
@@ -227,6 +243,19 @@
                 mDownPos.set(ev.getX(), ev.getY());
                 mLastPos.set(mDownPos);
                 mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
+                mTouchThresholdCrossed = false;
+
+                // Clean up the old interaction handler
+                if (mInteractionHandler != null) {
+                    final BaseSwipeInteractionHandler handler = mInteractionHandler;
+                    mMainThreadExecutor.execute(handler::reset);
+                    mInteractionHandler = null;
+                }
+
+                // Start the window animation on down to give more time for launcher to draw
+                if (!isUsingScreenShot()) {
+                    startTouchTrackingForWindowAnimation();
+                }
 
                 if (mVelocityTracker == null) {
                     mVelocityTracker = VelocityTracker.obtain();
@@ -234,11 +263,6 @@
                     mVelocityTracker.clear();
                 }
                 mVelocityTracker.addMovement(ev);
-                if (mInteractionHandler != null) {
-                    mInteractionHandler.reset();
-                    mInteractionHandler = null;
-                }
-                mTouchThresholdCrossed = false;
 
                 Display display = getSystemService(WindowManager.class).getDefaultDisplay();
                 mDisplayRotation = display.getRotation();
@@ -278,7 +302,11 @@
                     if (mTouchThresholdCrossed) {
                         mStartDisplacement = Math.signum(displacement) * mTouchSlop;
 
-                        startTouchTracking();
+                        if (isUsingScreenShot()) {
+                            startTouchTrackingForScreenshotAnimation();
+                        }
+
+                        // Notify the handler that the gesture has actually started
                         mInteractionHandler.onGestureStarted();
 
                         // Notify the system that we have started tracking the event
@@ -298,7 +326,7 @@
                 TraceHelper.endSection("TouchInt");
 
                 finishTouchTracking();
-                mCurrentConsumer = mNoOpTouchConsumer;
+                mEventQueue.setConsumer(mNoOpTouchConsumer);
                 break;
             }
         }
@@ -312,72 +340,83 @@
         return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0;
     }
 
+    private boolean isUsingScreenShot() {
+        return Utilities.getPrefs(this).getBoolean("pref_use_screenshot_animation", true);
+    }
+
     /**
      * Called when the gesture has started.
      */
-    private void startTouchTracking() {
-        if (Utilities.getPrefs(this).getBoolean("pref_use_screenshot_animation", true)) {
-            // Create the shared handler
-            final NavBarSwipeInteractionHandler handler =
-                    new NavBarSwipeInteractionHandler(mRunningTask, this, INTERACTION_NORMAL);
+    private void startTouchTrackingForScreenshotAnimation() {
+        // Create the shared handler
+        final NavBarSwipeInteractionHandler handler =
+                new NavBarSwipeInteractionHandler(mRunningTask, this, INTERACTION_NORMAL);
 
-            TraceHelper.partitionSection("TouchInt", "Thershold crossed ");
+        TraceHelper.partitionSection("TouchInt", "Thershold crossed ");
 
-            // Start the recents activity on a background thread
-            BackgroundExecutor.get().submit(() -> {
-                // Get the snap shot before
-                handler.setTaskSnapshot(getCurrentTaskSnapshot());
+        // Start the recents activity on a background thread
+        BackgroundExecutor.get().submit(() -> {
+            // Get the snap shot before
+            handler.setTaskSnapshot(getCurrentTaskSnapshot());
 
-                // Start the launcher activity with our custom handler
-                Intent homeIntent = handler.addToIntent(new Intent(mHomeIntent));
-                startActivity(homeIntent, ActivityOptions.makeCustomAnimation(this, 0, 0).toBundle());
-                TraceHelper.partitionSection("TouchInt", "Home started");
-            });
+            // Start the launcher activity with our custom handler
+            Intent homeIntent = handler.addToIntent(new Intent(mHomeIntent));
+            startActivity(homeIntent, ActivityOptions.makeCustomAnimation(this, 0, 0).toBundle());
+            TraceHelper.partitionSection("TouchInt", "Home started");
+        });
 
-            // Preload the plan
-            mRecentsModel.loadTasks(mRunningTask.id, null);
-            mInteractionHandler = handler;
-            mInteractionHandler.setGestureEndCallback(() ->  mInteractionHandler = null);
-        } else {
+        // Preload the plan
+        mRecentsModel.loadTasks(mRunningTask.id, null);
+        mInteractionHandler = handler;
+        mInteractionHandler.setGestureEndCallback(() ->  mInteractionHandler = null);
+    }
 
-            // Create the shared handler
-            final WindowTransformSwipeHandler handler =
-                    new WindowTransformSwipeHandler(mRunningTask, this);
+    private void startTouchTrackingForWindowAnimation() {
+        // Create the shared handler
+        final WindowTransformSwipeHandler handler =
+                new WindowTransformSwipeHandler(mRunningTask, this);
+        BackgroundExecutor.get().submit(() -> {
+            ActivityManagerWrapper.getInstance().startRecentsActivity(mHomeIntent,
+                    new AssistDataReceiver() {
+                        @Override
+                        public void onHandleAssistData(Bundle bundle) {
+                            // Pass to AIAI
+                        }
+                    },
+                    new RecentsAnimationListener() {
+                        public void onAnimationStart(
+                                RecentsAnimationControllerCompat controller,
+                                RemoteAnimationTargetCompat[] apps) {
+                            if (mInteractionHandler == handler) {
+                                handler.setRecentsAnimation(controller, apps);
 
-            BackgroundExecutor.get().submit(() -> {
-                ActivityManagerWrapper.getInstance().startRecentsActivity(mHomeIntent,
-                        new AssistDataReceiver() {
-                            @Override
-                            public void onHandleAssistData(Bundle bundle) {
-                                // Pass to AIAI
+                            } else {
+                                controller.finish(false /* toHome */);
                             }
-                        },
-                        new RecentsAnimationListener() {
-                            public void onAnimationStart(
-                                    RecentsAnimationControllerCompat controller,
-                                    RemoteAnimationTargetCompat[] apps) {
-                                if (mInteractionHandler == handler) {
-                                    handler.setRecentsAnimation(controller, apps);
+                        }
 
-                                } else {
-                                    controller.finish(false /* toHome */);
-                                }
+                        public void onAnimationCanceled() {
+                            if (mInteractionHandler == handler) {
+                                handler.setRecentsAnimation(null, null);
                             }
+                        }
+                    }, null, null);
+        });
 
-                            public void onAnimationCanceled() {
-                                if (mInteractionHandler == handler) {
-                                    handler.setRecentsAnimation(null, null);
-                                }
-                            }
-                        }, null, null);
-            });
-
-            // Preload the plan
-            mRecentsModel.loadTasks(mRunningTask.id, null);
-            mInteractionHandler = handler;
-            mInteractionHandler.initWhenReady();
-            mInteractionHandler.setGestureEndCallback(() ->  mInteractionHandler = null);
-        }
+        // Preload the plan
+        mRecentsModel.loadTasks(mRunningTask.id, null);
+        mInteractionHandler = handler;
+        handler.setGestureEndCallback(() -> {
+            if (handler == mInteractionHandler) {
+                mInteractionHandler = null;
+            }
+        });
+        handler.setLauncherOnDrawCallback(() -> {
+            if (handler == mInteractionHandler) {
+                mEventQueue.setInterimChoreographer(null);
+            }
+        });
+        mMainThreadExecutor.execute(handler::initWhenReady);
     }
 
     private void updateTouchTracking(@InteractionType int interactionType) {
@@ -398,6 +437,11 @@
                     : isNavBarOnLeft() ? -mVelocityTracker.getXVelocity(mActivePointerId)
                     : mVelocityTracker.getYVelocity(mActivePointerId);
             mInteractionHandler.onGestureEnded(velocity);
+        } else if (!isUsingScreenShot()) {
+            // Since we start touch tracking on DOWN, we may reach this state without actually
+            // starting the gesture. In that case, just cleanup immediately.
+            final BaseSwipeInteractionHandler handler = mInteractionHandler;
+            mMainThreadExecutor.execute(handler::reset);
         }
         mVelocityTracker.recycle();
         mVelocityTracker = null;
@@ -468,7 +512,7 @@
                     case ACTION_POINTER_UP:
                     case ACTION_POINTER_DOWN:
                         if (!mTrackingStarted) {
-                            mCurrentConsumer = mNoOpTouchConsumer;
+                            mEventQueue.setConsumer(mNoOpTouchConsumer);
                         }
                         break;
                     case ACTION_MOVE: {
@@ -492,7 +536,7 @@
             }
 
             if (action == ACTION_UP || action == ACTION_CANCEL) {
-                mCurrentConsumer = mNoOpTouchConsumer;
+                mEventQueue.setConsumer(mNoOpTouchConsumer);
             }
         }
 
@@ -506,6 +550,15 @@
         }
     }
 
+    private void initBackgroundChoreographer() {
+        if (sRemoteUiThread == null) {
+            sRemoteUiThread = new HandlerThread("remote-ui");
+            sRemoteUiThread.start();
+        }
+        new Handler(sRemoteUiThread.getLooper()).post(() ->
+                mBackgroundThreadChoreographer = Choreographer.getInstance());
+    }
+
     public static boolean isInteractionQuick(@InteractionType int interactionType) {
         return interactionType == INTERACTION_QUICK_SCRUB ||
                 interactionType == INTERACTION_QUICK_SWITCH;
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index e9b22a2..1351973 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -15,8 +15,6 @@
  */
 package com.android.quickstep;
 
-import static android.view.MotionEvent.ACTION_UP;
-
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.TouchInteractionService.INTERACTION_NORMAL;
@@ -33,33 +31,31 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.Build;
-import android.os.UserHandle;
+import android.os.Looper;
 import android.support.annotation.UiThread;
+import android.support.annotation.WorkerThread;
 import android.view.View;
+import android.view.ViewTreeObserver.OnDrawListener;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.states.InternalStateHandler;
 import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.TouchInteractionService.InteractionType;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.BackgroundExecutor;
-import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.TransactionCompat;
@@ -70,17 +66,18 @@
 
     // Launcher UI related states
     private static final int STATE_LAUNCHER_READY = 1 << 0;
-    private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE = 1 << 1;
+    private static final int STATE_LAUNCHER_DRAWN = 1 << 1;
+    private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE = 1 << 2;
 
     // Internal initialization states
-    private static final int STATE_APP_CONTROLLER_RECEIVED = 1 << 2;
+    private static final int STATE_APP_CONTROLLER_RECEIVED = 1 << 3;
 
     // Interaction finish states
-    private static final int STATE_SCALED_SNAPSHOT_RECENTS = 1 << 3;
-    private static final int STATE_SCALED_SNAPSHOT_APP = 1 << 4;
+    private static final int STATE_SCALED_SNAPSHOT_RECENTS = 1 << 4;
+    private static final int STATE_SCALED_SNAPSHOT_APP = 1 << 5;
 
     private static final int LAUNCHER_UI_STATES =
-            STATE_LAUNCHER_READY | STATE_ACTIVITY_MULTIPLIER_COMPLETE;
+            STATE_LAUNCHER_READY | STATE_LAUNCHER_DRAWN | STATE_ACTIVITY_MULTIPLIER_COMPLETE;
 
     private static final long MAX_SWIPE_DURATION = 200;
     private static final long MIN_SWIPE_DURATION = 80;
@@ -103,8 +100,10 @@
     // visible.
     private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
 
-    private final Task mRunningTask;
+    private final MainThreadExecutor mMainExecutor = new MainThreadExecutor();
+
     private final Context mContext;
+    private final int mRunningTaskId;
 
     private MultiStateCallback mStateCallback;
     private boolean mControllerStateAnimation;
@@ -115,6 +114,8 @@
     private RecentsView mRecentsView;
     private QuickScrubController mQuickScrubController;
 
+    private Runnable mLauncherDrawnCallback;
+
     private boolean mWasLauncherAlreadyVisible;
 
     private float mCurrentDisplacement;
@@ -122,28 +123,12 @@
     private @InteractionType int mInteractionType = INTERACTION_NORMAL;
     private boolean mStartedQuickScrubFromHome;
 
-    private RecentsAnimationControllerCompat mRecentsAnimationController;
-    private RemoteAnimationTargetCompat[] mRecentsAnimationApps;
-    private boolean mRecentsAnimationInputConsumerEnabled;
+    private final RecentsAnimationWrapper mRecentsAnimationWrapper = new RecentsAnimationWrapper();
     private Matrix mTmpMatrix = new Matrix();
 
-    private final InputConsumerController mInputConsumerController;
-    private final InputConsumerController.TouchListener mInputConsumerTouchListener =
-            (ev) -> {
-                if (ev.getActionMasked() == ACTION_UP) {
-                    onGestureInterruptEnd();
-                }
-                return true;
-            };
-
     WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context) {
-        // TODO: We need a better way for this
-        TaskKey taskKey = new TaskKey(runningTaskInfo.id, 0, null, UserHandle.myUserId(), 0);
-        mRunningTask = new Task(taskKey, null, null, "", "", Color.BLACK, Color.BLACK,
-                true, false, false, false, null, 0, null, false);
         mContext = context;
-        mInputConsumerController = InputConsumerController.getRecentsAnimationInputConsumer();
-
+        mRunningTaskId = runningTaskInfo.id;
 
         WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
 
@@ -173,6 +158,19 @@
                 this::reset);
         mStateCallback.addCallback(STATE_LAUNCHER_READY | STATE_SCALED_SNAPSHOT_RECENTS,
                 this::reset);
+
+        mStateCallback.addCallback(STATE_LAUNCHER_READY | STATE_LAUNCHER_DRAWN,
+                mLauncherDrawnCallback);
+    }
+
+    private void setStateOnUiThread(int stateFlag) {
+        mMainExecutor.execute(() -> mStateCallback.setState(stateFlag));
+    }
+
+    public void setLauncherOnDrawCallback(Runnable callback) {
+        mLauncherDrawnCallback = callback;
+        mStateCallback.addCallback(STATE_LAUNCHER_READY | STATE_LAUNCHER_DRAWN,
+                mLauncherDrawnCallback);
     }
 
     private void initTransitionEndpoints(DeviceProfile dp) {
@@ -220,21 +218,39 @@
                     .createAnimationToNewWorkspace(OVERVIEW, accuracy);
             mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
 
-            mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
+            mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_LAUNCHER_DRAWN);
         } else {
+            TraceHelper.beginSection("WTS-init");
             launcher.getStateManager().goToState(OVERVIEW, false);
+            TraceHelper.partitionSection("WTS-init", "State changed");
 
             // TODO: Implement a better animation for fading in
             View rootView = launcher.getRootView();
             rootView.setAlpha(0);
-            rootView.animate().alpha(1)
-                    .setDuration(getFadeInDuration())
-                    .withEndAction(() -> mStateCallback.setState(
-                            launcher == mLauncher ? STATE_ACTIVITY_MULTIPLIER_COMPLETE : 0));
+            rootView.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
+
+                @Override
+                public void onDraw() {
+                    TraceHelper.endSection("WTS-init", "Launcher frame is drawn");
+                    rootView.post(() ->
+                            rootView.getViewTreeObserver().removeOnDrawListener(this));
+                    if (launcher != mLauncher) {
+                        return;
+                    }
+
+                    if ((mStateCallback.getState() & STATE_LAUNCHER_DRAWN) == 0) {
+                        mStateCallback.setState(STATE_LAUNCHER_DRAWN);
+                        rootView.animate().alpha(1)
+                                .setDuration(getFadeInDuration())
+                                .withEndAction(() -> mStateCallback.setState(launcher == mLauncher
+                                        ? STATE_ACTIVITY_MULTIPLIER_COMPLETE : 0));
+                    }
+                }
+            });
         }
 
         mRecentsView = mLauncher.getOverviewPanel();
-        mRecentsView.showTask(mRunningTask);
+        mRecentsView.showTask(mRunningTaskId);
         mWasLauncherAlreadyVisible = alreadyOnHome;
         mLauncherLayoutListener = new LauncherLayoutListener(mLauncher, this);
         mLauncher.getDragLayer().addView(mLauncherLayoutListener);
@@ -277,7 +293,7 @@
         }
     }
 
-    @UiThread
+    @WorkerThread
     public void updateDisplacement(float displacement) {
         mCurrentDisplacement = displacement;
 
@@ -329,70 +345,60 @@
                     AnimatorPlaybackController.wrap(anim, mTransitionDragLength * 2);
             mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
         }
-
-        // In case the transition length changed (which should never happen, redo everything
-        updateDisplacement(mCurrentDisplacement);
     }
 
-    @UiThread
+    @WorkerThread
     private void updateFinalShift() {
         if (mStartedQuickScrubFromHome) {
             return;
         }
 
         float shift = mCurrentShift.value;
-        mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect);
-        float scale = (float) mCurrentRect.width() / mSourceRect.width();
-        if (mRecentsAnimationApps != null) {
-            mClipRect.left = mSourceRect.left;
-            mClipRect.top = (int) (mStableInsets.top * shift);
-            mClipRect.bottom = (int) (mDp.heightPx - (mStableInsets.bottom * shift));
-            mClipRect.right = mSourceRect.right;
 
-            mTmpMatrix.setScale(scale, scale, 0, 0);
-            mTmpMatrix.postTranslate(mCurrentRect.left - mStableInsets.left * scale * shift,
-                    mCurrentRect.top - mStableInsets.top * scale * shift);
-            TransactionCompat transaction = new TransactionCompat();
-            for (RemoteAnimationTargetCompat app : mRecentsAnimationApps) {
-                if (app.mode == MODE_CLOSING) {
-                    transaction.setMatrix(app.leash, mTmpMatrix)
-                            .setWindowCrop(app.leash, mClipRect)
-                            .show(app.leash);
+        synchronized (mRecentsAnimationWrapper) {
+            if (mRecentsAnimationWrapper.controller != null) {
+                mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect);
+                float scale = (float) mCurrentRect.width() / mSourceRect.width();
+
+                mClipRect.left = mSourceRect.left;
+                mClipRect.top = (int) (mStableInsets.top * shift);
+                mClipRect.bottom = (int) (mDp.heightPx - (mStableInsets.bottom * shift));
+                mClipRect.right = mSourceRect.right;
+
+                mTmpMatrix.setScale(scale, scale, 0, 0);
+                mTmpMatrix.postTranslate(mCurrentRect.left - mStableInsets.left * scale * shift,
+                        mCurrentRect.top - mStableInsets.top * scale * shift);
+                TransactionCompat transaction = new TransactionCompat();
+                for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
+                    if (app.mode == MODE_CLOSING) {
+                        transaction.setMatrix(app.leash, mTmpMatrix)
+                                .setWindowCrop(app.leash, mClipRect)
+                                .show(app.leash);
+                    }
                 }
+                transaction.apply();
             }
-            transaction.apply();
         }
 
         if (mLauncherTransitionController != null) {
-            mLauncherTransitionController.setPlayFraction(shift);
+            if (Looper.getMainLooper() == Looper.myLooper()) {
+                mLauncherTransitionController.setPlayFraction(shift);
+            } else {
+                // The fling operation completed even before the launcher was drawn
+                mMainExecutor.execute(() -> mLauncherTransitionController.setPlayFraction(shift));
+            }
         }
     }
 
     public void setRecentsAnimation(RecentsAnimationControllerCompat controller,
             RemoteAnimationTargetCompat[] apps) {
-        mRecentsAnimationController = controller;
-        if (mRecentsAnimationInputConsumerEnabled) {
-            BackgroundExecutor.get().submit(() ->
-                    mRecentsAnimationController.setInputConsumerEnabled(true));
-        }
-        mRecentsAnimationApps = apps;
-        mStateCallback.setState(STATE_APP_CONTROLLER_RECEIVED);
+        mRecentsAnimationWrapper.setController(controller, apps);
+        setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
     }
 
-    public void onGestureStarted() {
-        mInputConsumerController.unregisterInputConsumer();
-        mInputConsumerController.registerInputConsumer();
-        mInputConsumerController.setTouchListener(mInputConsumerTouchListener);
+    public void onGestureStarted() { }
 
-        if (mRecentsAnimationController != null) {
-            BackgroundExecutor.get().submit(() ->
-                mRecentsAnimationController.setInputConsumerEnabled(true));
-        } else {
-            mRecentsAnimationInputConsumerEnabled = true;
-        }
-    }
-
-    @UiThread
+    @WorkerThread
     public void onGestureEnded(float endVelocity) {
         Resources res = mContext.getResources();
         float flingThreshold = res.getDimension(R.dimen.quickstep_fling_threshold_velocity);
@@ -415,18 +421,6 @@
             }
         }
 
-        if (endShift == mCurrentShift.value) {
-            mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0)
-                    ? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS);
-        } else {
-            animateToProgress(endShift, duration);
-        }
-    }
-
-    @UiThread
-    public void onGestureInterruptEnd() {
-        final float endShift = 0;
-        final long duration = MAX_SWIPE_DURATION;
         animateToProgress(endShift, duration);
     }
 
@@ -437,7 +431,7 @@
         anim.addListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationSuccess(Animator animator) {
-                mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0)
+                setStateOnUiThread((Float.compare(mCurrentShift.value, 0) == 0)
                         ? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS);
             }
         });
@@ -446,12 +440,7 @@
 
     @UiThread
     private void resumeLastTask() {
-        if (mRecentsAnimationController != null) {
-            BackgroundExecutor.get().submit(() -> {
-                mRecentsAnimationController.setInputConsumerEnabled(false);
-                mRecentsAnimationController.finish(false /* toHome */);
-            });
-        }
+        mRecentsAnimationWrapper.finish(false /* toHome */);
     }
 
     public void reset() {
@@ -460,12 +449,13 @@
         if (mGestureEndCallback != null) {
             mGestureEndCallback.run();
         }
-        mInputConsumerController.unregisterInputConsumer();
 
-        // TODO: These should be done as part of ActivityOptions#OnAnimationStarted
-        mLauncher.getStateManager().reapplyState();
-        mLauncher.setOnResumeCallback(() -> mLauncherLayoutListener.close(false));
-        mLauncherTransitionController.setPlayFraction(1);
+        if (mLauncher != null) {
+            // TODO: These should be done as part of ActivityOptions#OnAnimationStarted
+            mLauncher.getStateManager().reapplyState();
+            mLauncher.setOnResumeCallback(() -> mLauncherLayoutListener.close(false));
+            mLauncherTransitionController.setPlayFraction(1);
+        }
         clearReference();
     }
 
@@ -485,7 +475,7 @@
             for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
                 TaskView taskView = (TaskView) mRecentsView.getPageAt(i);
                 // TODO: Match the keys directly
-                if (taskView.getTask().key.id != mRunningTask.key.id) {
+                if (taskView.getTask().key.id != mRunningTaskId) {
                     mRecentsView.snapToPage(i, QUICK_SWITCH_SNAP_DURATION);
                     taskView.postDelayed(() -> {taskView.launchTask(true);},
                             QUICK_SWITCH_SNAP_DURATION);
@@ -497,22 +487,21 @@
                 mQuickScrubController.snapToPageForCurrentQuickScrubSection();
             }
         } else {
-            if (mRecentsAnimationController != null) {
-                TransactionCompat transaction = new TransactionCompat();
-                for (RemoteAnimationTargetCompat app : mRecentsAnimationApps) {
-                    if (app.mode == MODE_CLOSING) {
-                        // Update the screenshot of the task
-                        final ThumbnailData thumbnail =
-                                mRecentsAnimationController.screenshotTask(app.taskId);
-                        mRecentsView.updateThumbnail(app.taskId, thumbnail);
+            synchronized (mRecentsAnimationWrapper) {
+                if (mRecentsAnimationWrapper.controller != null) {
+                    TransactionCompat transaction = new TransactionCompat();
+                    for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
+                        if (app.mode == MODE_CLOSING) {
+                            // Update the screenshot of the task
+                            final ThumbnailData thumbnail =
+                                    mRecentsAnimationWrapper.controller.screenshotTask(app.taskId);
+                            mRecentsView.updateThumbnail(app.taskId, thumbnail);
+                        }
                     }
+                    transaction.apply();
                 }
-                transaction.apply();
-                BackgroundExecutor.get().submit(() -> {
-                    mRecentsAnimationController.setInputConsumerEnabled(false);
-                    mRecentsAnimationController.finish(true /* toHome */);
-                });
             }
+            mRecentsAnimationWrapper.finish(true /* toHome */);
         }
     }
 
diff --git a/res/layout/system_shortcut_icon_only.xml b/res/layout/system_shortcut_icon_only.xml
index c59cb53..b8b5b8c 100644
--- a/res/layout/system_shortcut_icon_only.xml
+++ b/res/layout/system_shortcut_icon_only.xml
@@ -20,5 +20,6 @@
     android:layout_height="@dimen/system_shortcut_header_icon_touch_size"
     android:background="?android:attr/selectableItemBackgroundBorderless"
     android:tint="?android:attr/textColorHint"
+    android:tintMode="src_in"
     android:padding="@dimen/system_shortcut_header_icon_padding"
     android:theme="@style/PopupItem" />
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c1883b1..85443ed 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -408,9 +408,7 @@
         mAppTransitionManager = Utilities.getOverrideObject(LauncherAppTransitionManager.class,
                         this, R.string.app_transition_manager_class);
 
-        if (!isInMultiWindowModeCompat()) {
-            mAppTransitionManager.registerRemoteAnimations();
-        }
+        mAppTransitionManager.registerRemoteAnimations();
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onCreate(savedInstanceState);
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 1b169f5..929606e 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -20,7 +20,6 @@
 import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
 
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.ContentProviderOperation;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -40,21 +39,18 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.model.AddWorkspaceItemsTask;
+import com.android.launcher3.model.BaseModelUpdateTask;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.CacheDataUpdatedTask;
-import com.android.launcher3.model.BaseModelUpdateTask;
 import com.android.launcher3.model.LoaderResults;
 import com.android.launcher3.model.LoaderTask;
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.model.PackageInstallStateChangedTask;
-import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.model.PackageUpdatedTask;
 import com.android.launcher3.model.ShortcutsChangedTask;
 import com.android.launcher3.model.UserLockStateChangedTask;
-import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index dc3de18..338bdf3 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -538,7 +538,7 @@
 
     public void onScrollUpEnd() {
         if (mUsingTabs) {
-            ((PersonalWorkSlidingTabStrip) findViewById(R.id.tabs)).peekWorkTabIfNecessary();
+            ((PersonalWorkSlidingTabStrip) findViewById(R.id.tabs)).highlightWorkTabIfNecessary();
         }
     }
 
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index 9fc4e2e..05cd655 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.allapps;
 
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.graphics.Canvas;
@@ -30,7 +29,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.util.Themes;
 
 /**
@@ -39,8 +37,6 @@
 public class PersonalWorkSlidingTabStrip extends LinearLayout {
     private static final int POSITION_PERSONAL = 0;
     private static final int POSITION_WORK = 1;
-    private static final int PEEK_DURATION = 1000;
-    private static final float PEAK_OFFSET = 0.4f;
 
     private static final String KEY_SHOWED_PEEK_WORK_TAB = "showed_peek_work_tab";
 
@@ -139,25 +135,22 @@
             mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
     }
 
-    public void peekWorkTabIfNecessary() {
+    public void highlightWorkTabIfNecessary() {
         if (mSharedPreferences.getBoolean(KEY_SHOWED_PEEK_WORK_TAB, false)) {
             return;
         }
         if (mIndicatorPosition != POSITION_PERSONAL) {
             return;
         }
-        peekWorkTab();
+        highlightWorkTab();
         mSharedPreferences.edit().putBoolean(KEY_SHOWED_PEEK_WORK_TAB, true).apply();
     }
 
-    private void peekWorkTab() {
-        final boolean isRtl = Utilities.isRtl(getResources());
-        ValueAnimator animator = ValueAnimator.ofFloat(0, isRtl ? 1 - PEAK_OFFSET : PEAK_OFFSET, 0);
-        animator.setDuration(PEEK_DURATION);
-        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        animator.addUpdateListener(
-                animation -> updateIndicatorPosition(mIndicatorPosition,
-                        (float) animation.getAnimatedValue()));
-        animator.start();
+    private void highlightWorkTab() {
+        View v = getChildAt(POSITION_WORK);
+        v.post(() -> {
+            v.setPressed(true);
+            v.setPressed(false);
+        });
     }
 }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 883c33d..00dd3aa 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -54,7 +54,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIconPreviewVerifier;
-import com.android.launcher3.graphics.IconNormalizer;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.provider.ImportDataTask;
diff --git a/src/com/android/launcher3/model/ModelPreload.java b/src/com/android/launcher3/model/ModelPreload.java
new file mode 100644
index 0000000..6f33bed
--- /dev/null
+++ b/src/com/android/launcher3/model/ModelPreload.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import android.content.Context;
+import android.support.annotation.WorkerThread;
+import android.util.Log;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Utility class to preload LauncherModel
+ */
+public class ModelPreload implements ModelUpdateTask {
+
+    private static final String TAG = "ModelPreload";
+
+    private LauncherAppState mApp;
+    private LauncherModel mModel;
+    private BgDataModel mBgDataModel;
+    private AllAppsList mAllAppsList;
+
+    @Override
+    public final void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel,
+            AllAppsList allAppsList, Executor uiExecutor) {
+        mApp = app;
+        mModel = model;
+        mBgDataModel = dataModel;
+        mAllAppsList = allAppsList;
+    }
+
+    @Override
+    public final void run() {
+        if (!mModel.isModelLoaded()) {
+            Log.d(TAG, "Workspace not loaded, loading now");
+            mModel.startLoaderForResults(
+                    new LoaderResults(mApp, mBgDataModel, mAllAppsList, 0, null));
+        }
+        Log.d(TAG, "Preload completed : " + mModel.isModelLoaded());
+        onComplete(mModel.isModelLoaded());
+    }
+
+    /**
+     * Called when the task is complete
+     */
+    @WorkerThread
+    public void onComplete(boolean isSuccess) { }
+
+    public void start(Context context) {
+        LauncherAppState.getInstance(context).getModel().enqueueModelUpdateTask(this);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/util/VerticalSwipeController.java b/src/com/android/launcher3/util/VerticalSwipeController.java
index 5d47cd2..29477e3 100644
--- a/src/com/android/launcher3/util/VerticalSwipeController.java
+++ b/src/com/android/launcher3/util/VerticalSwipeController.java
@@ -259,7 +259,7 @@
         });
 
         float nextFrameProgress = Utilities.boundToRange(
-                progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
+                progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
 
         ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
         anim.setFloatValues(nextFrameProgress, targetState == mToState ? 1f : 0f);