Merge "Log dismissing or launching recent tasks" into ub-launcher3-master
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index af81a59..a2567c8 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -57,6 +57,7 @@
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.quickstep.RecentsAnimationInterpolator;
 import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
+import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
@@ -106,7 +107,7 @@
     private DeviceProfile mDeviceProfile;
     private View mFloatingView;
 
-    private RemoteAnimationRunnerCompat mRemoteAnimationOverride;
+    private RemoteAnimationProvider mRemoteAnimationProvider;
 
     private final AnimatorListenerAdapter mReapplyStateListener = new AnimatorListenerAdapter() {
         @Override
@@ -179,8 +180,8 @@
         return getDefaultActivityLaunchOptions(launcher, v);
     }
 
-    public void setRemoteAnimationOverride(RemoteAnimationRunnerCompat remoteAnimationOverride) {
-        mRemoteAnimationOverride = remoteAnimationOverride;
+    public void setRemoteAnimationProvider(RemoteAnimationProvider animationProvider) {
+        mRemoteAnimationProvider = animationProvider;
     }
 
     /**
@@ -683,52 +684,33 @@
     private RemoteAnimationRunnerCompat getWallpaperOpenRunner() {
         return new LauncherAnimationRunner(mHandler) {
             @Override
-            public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats,
-                    Runnable runnable) {
-                if (mLauncher.getStateManager().getState().overviewUi
-                        && mRemoteAnimationOverride != null) {
-                    // This transition is only used for the fallback activity and should not be
-                    // managed here (but necessary to implement here since the defined remote
-                    // animation currently takes precendence over the one defined in the activity
-                    // options).
-                    mRemoteAnimationOverride.onAnimationStart(targetCompats, runnable);
-                    return;
-                }
-                super.onAnimationStart(targetCompats, runnable);
-            }
-
-            @Override
-            public void onAnimationCancelled() {
-                if (mLauncher.getStateManager().getState().overviewUi
-                        && mRemoteAnimationOverride != null) {
-                    // This transition is only used for the fallback activity and should not be
-                    // managed here (but necessary to implement here since the defined remote
-                    // animation currently takes precendence over the one defined in the activity
-                    // options).
-                    mRemoteAnimationOverride.onAnimationCancelled();
-                    return;
-                }
-                super.onAnimationCancelled();
-            }
-
-            @Override
             public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
-                AnimatorSet anim = new AnimatorSet();
-                anim.play(getClosingWindowAnimators(targetCompats));
-
-                // Normally, we run the launcher content animation when we are transitioning home,
-                // but if home is already visible, then we don't want to animate the contents of
-                // launcher unless we know that we are animating home as a result of the home button
-                // press with quickstep, which will result in launcher being started on touch down,
-                // prior to the animation home (and won't be in the targets list because it is
-                // already visible). In that case, we force invisibility on touch down, and only
-                // reset it after the animation to home is initialized.
-                if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)
-                        || mLauncher.isForceInvisible()) {
-                    // Only register the content animation for cancellation when state changes
-                    mLauncher.getStateManager().setCurrentAnimation(anim);
-                    createLauncherResumeAnimation(anim);
+                AnimatorSet anim = null;
+                RemoteAnimationProvider provider = mRemoteAnimationProvider;
+                if (provider != null) {
+                    anim = provider.createWindowAnimation(targetCompats);
                 }
+
+                if (anim == null) {
+                    anim = new AnimatorSet();
+                    anim.play(getClosingWindowAnimators(targetCompats));
+
+                    // Normally, we run the launcher content animation when we are transitioning
+                    // home, but if home is already visible, then we don't want to animate the
+                    // contents of launcher unless we know that we are animating home as a result
+                    // of the home button press with quickstep, which will result in launcher being
+                    // started on touch down, prior to the animation home (and won't be in the
+                    // targets list because it is already visible). In that case, we force
+                    // invisibility on touch down, and only reset it after the animation to home
+                    // is initialized.
+                    if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)
+                            || mLauncher.isForceInvisible()) {
+                        // Only register the content animation for cancellation when state changes
+                        mLauncher.getStateManager().setCurrentAnimation(anim);
+                        createLauncherResumeAnimation(anim);
+                    }
+                }
+
                 mLauncher.setForceInvisible(false);
                 return anim;
             }
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index 0d1038a..27f1698 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -15,13 +15,16 @@
  */
 package com.android.launcher3;
 
-import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
-
 import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
 import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
 
 import com.android.launcher3.states.InternalStateHandler;
 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+import com.android.quickstep.util.RemoteAnimationProvider;
 
 import java.util.function.BiPredicate;
 
@@ -30,15 +33,33 @@
 
     private final BiPredicate<Launcher, Boolean> mOnInitListener;
 
+    private RemoteAnimationProvider mRemoteAnimationProvider;
+
     public LauncherInitListener(BiPredicate<Launcher, Boolean> onInitListener) {
         mOnInitListener = onInitListener;
     }
 
     @Override
     protected boolean init(Launcher launcher, boolean alreadyOnHome) {
-        // For the duration of the gesture, lock the screen orientation to ensure that we do not
-        // rotate mid-quickscrub
-        launcher.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
+        if (mRemoteAnimationProvider != null) {
+            LauncherAppTransitionManagerImpl appTransitionManager =
+                    (LauncherAppTransitionManagerImpl) launcher.getAppTransitionManager();
+
+            // Set a one-time animation provider. After the first call, this will get cleared.
+            // TODO: Probably also check the intended target id.
+            appTransitionManager.setRemoteAnimationProvider((targets) -> {
+
+                // On the first call clear the reference.
+                appTransitionManager.setRemoteAnimationProvider(null);
+                RemoteAnimationProvider provider = mRemoteAnimationProvider;
+                mRemoteAnimationProvider = null;
+
+                if (provider != null && launcher.getStateManager().getState().overviewUi) {
+                    return provider.createWindowAnimation(targets);
+                }
+                return null;
+            });
+        }
         return mOnInitListener.test(launcher, alreadyOnHome);
     }
 
@@ -49,6 +70,18 @@
 
     @Override
     public void unregister() {
+        mRemoteAnimationProvider = null;
         clearReference();
     }
+
+    @Override
+    public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
+            Context context, Handler handler, long duration) {
+        mRemoteAnimationProvider = animProvider;
+
+        register();
+
+        Bundle options = animProvider.toActivityOptions(handler, duration).toBundle();
+        context.startActivity(addToIntent(new Intent((intent))), options);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
index 355b88d..30ceb43 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
@@ -60,12 +60,13 @@
 
     @Override
     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
-        if (fromState == ALL_APPS) {
+        if (fromState == ALL_APPS && !isDragTowardPositive) {
             // Should swipe down go to OVERVIEW instead?
             return TouchInteractionService.isConnected() ?
                     mLauncher.getStateManager().getLastState() : NORMAL;
-        } else {
+        } else if (isDragTowardPositive) {
             return ALL_APPS;
         }
+        return fromState;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index d97b7b2..ec54732 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -110,7 +110,7 @@
     @Override
     public int getVisibleElements(Launcher launcher) {
         if (launcher.getDeviceProfile().isVerticalBarLayout()) {
-            return NONE;
+            return DRAG_HANDLE_INDICATOR;
         } else {
             return HOTSEAT_SEARCH_BOX | DRAG_HANDLE_INDICATOR |
                     (launcher.getAppsView().getFloatingHeaderView().hasVisibleContent()
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
index 5971576..012b545 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -141,7 +141,7 @@
 
     @Override
     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
-        if (fromState == ALL_APPS) {
+        if (fromState == ALL_APPS && !isDragTowardPositive) {
             // Should swipe down go to OVERVIEW instead?
             return TouchInteractionService.isConnected() ?
                     mLauncher.getStateManager().getLastState() : NORMAL;
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 3e96c44..d82b8f4 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -22,7 +22,6 @@
 
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Rect;
@@ -39,19 +38,17 @@
 import com.android.launcher3.LauncherAppTransitionManagerImpl;
 import com.android.launcher3.LauncherInitListener;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.views.LauncherLayoutListener;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.AssistDataReceiver;
 import com.android.systemui.shared.system.RecentsAnimationListener;
-import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
 
 import java.util.function.BiPredicate;
 
@@ -85,9 +82,6 @@
     void startRecentsFromSwipe(Intent intent, AssistDataReceiver assistDataReceiver,
             final RecentsAnimationListener remoteAnimationListener);
 
-    void startRecentsFromButton(Context context, Intent intent,
-            RecentsAnimationListener remoteAnimationListener);
-
     @UiThread
     @Nullable
     RecentsView getVisibleRecentsView();
@@ -214,24 +208,6 @@
                     intent, assistDataReceiver, remoteAnimationListener, null, null);
         }
 
-        @Override
-        public void startRecentsFromButton(Context context, Intent intent,
-                RecentsAnimationListener remoteAnimationListener) {
-            // We should use the remove animation for the fallback activity recents button case,
-            // it works better with PiP.  In Launcher, we have already registered the remote
-            // animation definition, which takes priority over explicitly defined remote
-            // animations in the provided activity options when starting the activity, so we
-            // just register a remote animation factory to get a callback to handle this.
-            LauncherAppTransitionManagerImpl appTransitionManager =
-                    (LauncherAppTransitionManagerImpl) getLauncher().getAppTransitionManager();
-            appTransitionManager.setRemoteAnimationOverride(new RecentsAnimationActivityOptions(
-                    remoteAnimationListener, () -> {
-                        // Once the controller is finished, also reset the remote animation override
-                        appTransitionManager.setRemoteAnimationOverride(null);
-                    }));
-            context.startActivity(intent);
-        }
-
         @Nullable
         @UiThread
         private Launcher getLauncher() {
@@ -360,19 +336,6 @@
                     intent, assistDataReceiver, remoteAnimationListener, null, null);
         }
 
-        @Override
-        public void startRecentsFromButton(Context context, Intent intent,
-                RecentsAnimationListener remoteAnimationListener) {
-            // We should use the remove animation for the fallback activity recents button case,
-            // it works better with PiP. For the fallback activity, we should not have registered
-            // the launcher app transition manager, so we should just start the remote animation here.
-            ActivityOptions options = ActivityOptionsCompat.makeRemoteAnimation(
-                    new RemoteAnimationAdapterCompat(
-                            new RecentsAnimationActivityOptions(remoteAnimationListener, null),
-                            10000, 10000));
-            context.startActivity(intent, options.toBundle());
-        }
-
         @Nullable
         @Override
         public RecentsView getVisibleRecentsView() {
@@ -403,5 +366,8 @@
         void register();
 
         void unregister();
+
+        void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
+                Context context, Handler handler, long duration);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 8e59578..2fb4127 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -16,68 +16,46 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 
-import android.animation.Animator;
+import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
-import android.app.ActivityManager.RecentTaskInfo;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityOptions;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
-import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.SystemClock;
-import android.os.UserHandle;
-import android.support.annotation.UiThread;
-import android.support.annotation.WorkerThread;
-import android.util.SparseArray;
+import android.util.Log;
+import android.view.View;
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.MainThreadExecutor;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.states.InternalStateHandler;
-import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
 import com.android.quickstep.ActivityControlHelper.FallbackActivityControllerHelper;
 import com.android.quickstep.ActivityControlHelper.LauncherActivityControllerHelper;
+import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.SysuiEventLogger;
 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.recents.view.AppTransitionAnimationSpecCompat;
-import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
-import com.android.systemui.shared.recents.view.RecentsTransition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.AssistDataReceiver;
-import com.android.systemui.shared.system.BackgroundExecutor;
-import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
-import com.android.systemui.shared.system.RecentsAnimationListener;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Consumer;
-
 /**
  * Helper class to handle various atomic commands for switching between Overview.
  */
 @TargetApi(Build.VERSION_CODES.P)
 public class OverviewCommandHelper extends InternalStateHandler {
 
-    private static final int RID_RESET_SWIPE_HANDLER = 0;
-    private static final int RID_CANCEL_CONTROLLER = 1;
-    private static final int RID_CANCEL_ZOOM_OUT_ANIMATION = 2;
-
     private static final long RECENTS_LAUNCH_DURATION = 200;
 
     private static final String TAG = "OverviewCommandHelper";
@@ -91,16 +69,7 @@
     public final Intent homeIntent;
     public final ComponentName launcher;
 
-    private final SparseArray<Runnable> mCurrentCommandFinishRunnables = new SparseArray<>();
-    // Monotonically increasing command ids.
-    private int mCurrentCommandId = 0;
-
     private long mLastToggleTime;
-    private WindowTransformSwipeHandler mWindowTransformSwipeHandler;
-
-    private final Point mWindowSize = new Point();
-    private final Rect mTaskTargetRect = new Rect();
-    private final RectF mTempTaskTargetRect = new RectF();
 
     public OverviewCommandHelper(Context context) {
         mContext = context;
@@ -132,112 +101,6 @@
         initWhenReady();
     }
 
-    @UiThread
-    private void addFinishCommand(int requestId, int id, Runnable action) {
-        if (requestId < mCurrentCommandId) {
-            action.run();
-        } else {
-            mCurrentCommandFinishRunnables.put(id, action);
-        }
-    }
-
-    @UiThread
-    private void clearFinishCommand(int requestId, int id) {
-        if (requestId == mCurrentCommandId) {
-            mCurrentCommandFinishRunnables.remove(id);
-        }
-    }
-
-    @UiThread
-    private void initSwipeHandler(ActivityControlHelper helper, long time,
-            Consumer<WindowTransformSwipeHandler> onAnimationInitCallback) {
-        final int commandId = mCurrentCommandId;
-        final RunningTaskInfo runningTask = ActivityManagerWrapper.getInstance().getRunningTask();
-        final int runningTaskId = runningTask.id;
-        final WindowTransformSwipeHandler handler =
-                new WindowTransformSwipeHandler(runningTask, mContext, time, helper);
-
-        // Preload the plan
-        mRecentsModel.loadTasks(runningTaskId, null);
-        mWindowTransformSwipeHandler = handler;
-
-        mTempTaskTargetRect.setEmpty();
-        handler.setGestureEndCallback(() -> {
-            if (mWindowTransformSwipeHandler == handler) {
-                mWindowTransformSwipeHandler = null;
-                mTempTaskTargetRect.setEmpty();
-            }
-            clearFinishCommand(commandId, RID_RESET_SWIPE_HANDLER);
-            clearFinishCommand(commandId, RID_CANCEL_CONTROLLER);
-        });
-        handler.initWhenReady();
-        addFinishCommand(commandId, RID_RESET_SWIPE_HANDLER, handler::reset);
-
-        TraceHelper.beginSection(TAG);
-        Runnable startActivity = () -> helper.startRecentsFromButton(mContext,
-                addToIntent(homeIntent),
-                new RecentsAnimationListener() {
-                    public void onAnimationStart(
-                            RecentsAnimationControllerCompat controller,
-                            RemoteAnimationTargetCompat[] apps, Rect homeContentInsets,
-                            Rect minimizedHomeBounds) {
-                        if (mWindowTransformSwipeHandler == handler) {
-                            TraceHelper.partitionSection(TAG, "Received");
-                            handler.onRecentsAnimationStart(controller, apps, homeContentInsets,
-                                    minimizedHomeBounds);
-                            mTempTaskTargetRect.set(handler.getTargetRect(mWindowSize));
-
-                            ThumbnailData thumbnail = mAM.getTaskThumbnail(runningTaskId,
-                                    true /* reducedResolution */);
-                            mMainThreadExecutor.execute(() -> {
-                                addFinishCommand(commandId,
-                                        RID_CANCEL_CONTROLLER, () -> controller.finish(true));
-                                if (commandId == mCurrentCommandId) {
-                                    onAnimationInitCallback.accept(handler);
-
-                                    // The animation has started, which means the other activity
-                                    // should be paused, lets update the thumbnail
-                                    handler.switchToScreenshotImmediate(thumbnail);
-                                }
-                            });
-                        } else {
-                            TraceHelper.endSection(TAG, "Finishing no handler");
-                            controller.finish(false /* toHome */);
-                        }
-                    }
-
-                    public void onAnimationCanceled() {
-                        TraceHelper.endSection(TAG, "Cancelled: " + handler);
-                        if (mWindowTransformSwipeHandler == handler) {
-                            handler.onRecentsAnimationCanceled();
-                        }
-                    }
-                });
-
-        // We should almost always get touch-town on background thread. This is an edge case
-        // when the background Choreographer has not yet initialized.
-        BackgroundExecutor.get().submit(startActivity);
-    }
-
-    @UiThread
-    private void startZoomOutAnim(final WindowTransformSwipeHandler handler) {
-        final int commandId = mCurrentCommandId;
-        ValueAnimator anim = ValueAnimator.ofInt(0, -handler.getTransitionLength());
-        anim.addUpdateListener((a) -> handler.updateDisplacement((Integer) a.getAnimatedValue()));
-        anim.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                handler.onGestureEnded(0);
-                clearFinishCommand(commandId, RID_CANCEL_ZOOM_OUT_ANIMATION);
-            }
-        });
-        handler.onGestureStarted();
-        anim.setDuration(RECENTS_LAUNCH_DURATION);
-        anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        anim.start();
-        addFinishCommand(commandId, RID_CANCEL_ZOOM_OUT_ANIMATION, anim::cancel);
-    }
-
     public void onOverviewToggle() {
         // If currently screen pinning, do not enter overview
         if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
@@ -245,45 +108,7 @@
         }
 
         ActivityManagerWrapper.getInstance().closeSystemWindows("recentapps");
-        long time = SystemClock.elapsedRealtime();
-        mMainThreadExecutor.execute(() -> {
-            long elapsedTime = time - mLastToggleTime;
-            mLastToggleTime = time;
-
-            mCurrentCommandId++;
-            mTempTaskTargetRect.round(mTaskTargetRect);
-            int runnableCount = mCurrentCommandFinishRunnables.size();
-            if (runnableCount > 0) {
-                for (int i = 0; i < runnableCount; i++) {
-                    mCurrentCommandFinishRunnables.valueAt(i).run();
-                }
-                mCurrentCommandFinishRunnables.clear();
-            }
-
-            // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows
-            //       the menu activity which takes window focus, prevening the right condition from
-            //       being run below
-            ActivityControlHelper helper = getActivityControlHelper();
-            RecentsView recents = helper.getVisibleRecentsView();
-            if (recents != null) {
-                // Launch the next task
-                recents.showNextTask();
-            } else {
-                if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
-                    // The user tried to launch back into overview too quickly, either after
-                    // launching an app, or before overview has actually shown, just ignore for now
-                    return;
-                }
-
-                // Start overview
-                if (helper.switchToRecentsIfVisible()) {
-                    SysuiEventLogger.writeDummyRecentsTransition(0);
-                    // Do nothing
-                } else {
-                    initSwipeHandler(helper, time, this::startZoomOutAnim);
-                }
-            }
-        });
+        mMainThreadExecutor.execute(new RecentsActivityCommand<>());
     }
 
     public void onOverviewShown() {
@@ -298,48 +123,6 @@
         );
     }
 
-    public void onOverviewHidden() {
-        getLauncher().runOnUiThread(() -> {
-                    if (isOverviewAlmostVisible()) {
-                        final RecentsView rv = getLauncher().getOverviewPanel();
-                        rv.launchNextTask();
-                    }
-                }
-        );
-    }
-
-    @WorkerThread
-    private void startLastTask() {
-        // TODO: This should go through recents model.
-        List<RecentTaskInfo> tasks = mAM.getRecentTasks(2, UserHandle.myUserId());
-        if (tasks.size() > 1) {
-            RecentTaskInfo rti = tasks.get(1);
-
-            final ActivityOptions options;
-            if (!mTaskTargetRect.isEmpty()) {
-                final Rect targetRect = new Rect(mTaskTargetRect);
-                targetRect.offset(Utilities.isRtl(mContext.getResources())
-                        ? - mTaskTargetRect.width() : mTaskTargetRect.width(), 0);
-                final AppTransitionAnimationSpecCompat specCompat =
-                        new AppTransitionAnimationSpecCompat(rti.id, null, targetRect);
-                AppTransitionAnimationSpecsFuture specFuture =
-                        new AppTransitionAnimationSpecsFuture(mMainThreadExecutor.getHandler()) {
-
-                    @Override
-                    public List<AppTransitionAnimationSpecCompat> composeSpecs() {
-                        return Collections.singletonList(specCompat);
-                    }
-                };
-                options = RecentsTransition.createAspectScaleAnimation(mContext,
-                        mMainThreadExecutor.getHandler(), true /* scaleUp */,
-                        specFuture, () -> {});
-            } else {
-                options = ActivityOptions.makeBasic();
-            }
-            mAM.startActivityFromRecents(rti.id, options);
-        }
-    }
-
     private boolean isOverviewAlmostVisible() {
         if (clearReference()) {
             return true;
@@ -370,4 +153,123 @@
             return new LauncherActivityControllerHelper();
         }
     }
+
+    private class RecentsActivityCommand<T extends BaseDraggingActivity> implements Runnable {
+
+        private final ActivityControlHelper<T> mHelper;
+        private final long mCreateTime;
+        private final int mRunningTaskId;
+
+        private ActivityInitListener mListener;
+        private T mActivity;
+
+        public RecentsActivityCommand() {
+            mHelper = getActivityControlHelper();
+            mCreateTime = SystemClock.elapsedRealtime();
+            mRunningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().id;
+
+            // Preload the plan
+            mRecentsModel.loadTasks(mRunningTaskId, null);
+        }
+
+        @Override
+        public void run() {
+            long elapsedTime = mCreateTime - mLastToggleTime;
+            mLastToggleTime = mCreateTime;
+
+            // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows
+            //       the menu activity which takes window focus, preventing the right condition from
+            //       being run below
+            RecentsView recents = mHelper.getVisibleRecentsView();
+            if (recents != null) {
+                // Launch the next task
+                recents.showNextTask();
+            } else {
+                if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
+                    // The user tried to launch back into overview too quickly, either after
+                    // launching an app, or before overview has actually shown, just ignore for now
+                    return;
+                }
+
+                // Start overview
+                if (mHelper.switchToRecentsIfVisible()) {
+                    SysuiEventLogger.writeDummyRecentsTransition(0);
+                    // Do nothing
+                } else {
+                    mListener = mHelper.createActivityInitListener(this::onActivityReady);
+                    mListener.registerAndStartActivity(homeIntent, this::createWindowAnimation,
+                            mContext, mMainThreadExecutor.getHandler(), RECENTS_LAUNCH_DURATION);
+                }
+            }
+        }
+
+        private boolean onActivityReady(T activity, Boolean wasVisible) {
+            activity.<RecentsView>getOverviewPanel().setCurrentTask(mRunningTaskId);
+            AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
+            mHelper.prepareRecentsUI(activity, wasVisible);
+            if (wasVisible) {
+                AnimatorPlaybackController controller =
+                        mHelper.createControllerForVisibleActivity(activity);
+                controller.dispatchOnStart();
+                ValueAnimator anim =
+                        controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
+                anim.setInterpolator(FAST_OUT_SLOW_IN);
+                anim.start();
+            }
+            mActivity = activity;
+            return false;
+        }
+
+        private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
+            if (mListener != null) {
+                mListener.unregister();
+            }
+            RemoteAnimationProvider.showOpeningTarget(targetCompats);
+            AnimatorSet anim = new AnimatorSet();
+            if (mActivity == null) {
+                Log.e(TAG, "Animation created, before activity");
+                anim.play(ValueAnimator.ofInt(0, 1).setDuration(100));
+                return anim;
+            }
+
+            RemoteAnimationTargetCompat closingTarget = null;
+            // Use the top closing app to determine the insets for the animation
+            for (RemoteAnimationTargetCompat target : targetCompats) {
+                if (target.mode == MODE_CLOSING) {
+                    closingTarget = target;
+                    break;
+                }
+            }
+            if (closingTarget == null) {
+                Log.e(TAG, "No closing app");
+                anim.play(ValueAnimator.ofInt(0, 1).setDuration(100));
+                return anim;
+            }
+
+            final ClipAnimationHelper clipHelper = new ClipAnimationHelper();
+
+            // At this point, the activity is already started and laid-out. Get the home-bounds
+            // relative to the screen using the rootView of the activity.
+            int loc[] = new int[2];
+            View rootView = mActivity.getRootView();
+            rootView.getLocationOnScreen(loc);
+            Rect homeBounds = new Rect(loc[0], loc[1],
+                    loc[0] + rootView.getWidth(), loc[1] + rootView.getHeight());
+            clipHelper.updateSource(homeBounds, closingTarget);
+
+            Rect targetRect = new Rect();
+            mHelper.getSwipeUpDestinationAndLength(
+                    mActivity.getDeviceProfile(), mActivity, targetRect);
+            clipHelper.updateTargetRect(targetRect);
+
+
+            ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
+            valueAnimator.setDuration(RECENTS_LAUNCH_DURATION).setInterpolator(FAST_OUT_SLOW_IN);
+            valueAnimator.addUpdateListener((v) -> {
+                clipHelper.applyTransform(targetCompats, (float) v.getAnimatedValue());
+            });
+            anim.play(valueAnimator);
+            return anim;
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
index 5bd606e..77f0e7a 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
@@ -16,9 +16,14 @@
 package com.android.quickstep;
 
 import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
 import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
 
 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+import com.android.quickstep.util.RemoteAnimationProvider;
 
 import java.lang.ref.WeakReference;
 import java.util.function.BiPredicate;
@@ -78,4 +83,13 @@
             return sCurrentActivity.get();
         }
     }
+
+    @Override
+    public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
+            Context context, Handler handler, long duration) {
+        register();
+
+        Bundle options = animProvider.toActivityOptions(handler, duration).toBundle();
+        context.startActivity(intent, options);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationActivityOptions.java b/quickstep/src/com/android/quickstep/RecentsAnimationActivityOptions.java
deleted file mode 100644
index a25e192..0000000
--- a/quickstep/src/com/android/quickstep/RecentsAnimationActivityOptions.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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 android.graphics.Rect;
-
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
-import com.android.systemui.shared.system.RecentsAnimationListener;
-import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.TransactionCompat;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-import java.util.function.Consumer;
-
-/**
- * Class to create activity options to emulate recents transition.
- */
-public class RecentsAnimationActivityOptions implements RemoteAnimationRunnerCompat {
-
-    private final RecentsAnimationListener mListener;
-    private final Runnable mFinishCallback;
-
-    public RecentsAnimationActivityOptions(RecentsAnimationListener listener,
-            Runnable finishCallback) {
-        mListener = listener;
-        mFinishCallback = finishCallback;
-    }
-
-    @Override
-    public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats,
-            Runnable runnable) {
-        showOpeningTarget(targetCompats);
-        RemoteRecentsAnimationControllerCompat dummyRecentsAnim =
-                new RemoteRecentsAnimationControllerCompat(() -> {
-                    runnable.run();
-                    if (mFinishCallback != null) {
-                        mFinishCallback.run();
-                    }
-                });
-
-        Rect insets = new Rect();
-        WindowManagerWrapper.getInstance().getStableInsets(insets);
-        mListener.onAnimationStart(dummyRecentsAnim, targetCompats, insets, null);
-    }
-
-    @Override
-    public void onAnimationCancelled() {
-        mListener.onAnimationCanceled();
-    }
-
-    private void showOpeningTarget(RemoteAnimationTargetCompat[] targetCompats) {
-        TransactionCompat t = new TransactionCompat();
-        for (RemoteAnimationTargetCompat target : targetCompats) {
-            int layer = target.mode == RemoteAnimationTargetCompat.MODE_CLOSING
-                    ? Integer.MAX_VALUE
-                    : target.prefixOrderIndex;
-            t.setLayer(target.leash, layer);
-            t.show(target.leash);
-        }
-        t.apply();
-    }
-
-    private class RemoteRecentsAnimationControllerCompat extends RecentsAnimationControllerCompat {
-
-        final Runnable mFinishCallback;
-
-        public RemoteRecentsAnimationControllerCompat(Runnable finishCallback) {
-            mFinishCallback = finishCallback;
-        }
-
-        @Override
-        public ThumbnailData screenshotTask(int taskId) {
-            return new ThumbnailData();
-        }
-
-        @Override
-        public void setInputConsumerEnabled(boolean enabled) { }
-
-        @Override
-        public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) { }
-
-        @Override
-        public void finish(boolean toHome) {
-            // This should never be called with toHome == false
-            if (mFinishCallback != null) {
-                mFinishCallback.run();
-            }
-        }
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index f6cf85a..d4c35e0 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -30,11 +30,8 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.Matrix;
-import android.graphics.Matrix.ScaleToFit;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
@@ -64,11 +61,11 @@
 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
 import com.android.quickstep.ActivityControlHelper.LayoutListener;
 import com.android.quickstep.TouchConsumer.InteractionType;
+import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.SysuiEventLogger;
 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.recents.utilities.RectFEvaluator;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -132,26 +129,8 @@
 
     private static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
 
-    // The bounds of the source app in device coordinates
-    private final Rect mSourceStackBounds = new Rect();
-    // The insets of the source app
-    private final Rect mSourceInsets = new Rect();
-    // The source app bounds with the source insets applied, in the source app window coordinates
-    private final RectF mSourceRect = new RectF();
-    // The bounds of the task view in launcher window coordinates
-    private final RectF mTargetRect = new RectF();
-    // Doesn't change after initialized, used as an anchor when changing mTargetRect
-    private final RectF mInitialTargetRect = new RectF();
-    // The insets to be used for clipping the app window, which can be larger than mSourceInsets
-    // if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In
-    // app window coordinates.
-    private final RectF mSourceWindowClipInsets = new RectF();
+    private final ClipAnimationHelper mClipAnimationHelper = new ClipAnimationHelper();
 
-    // The bounds of launcher (not including insets) in device coordinates
-    private final Rect mHomeStackBounds = new Rect();
-    // The clip rect in source app window coordinates
-    private final Rect mClipRect = new Rect();
-    private final RectFEvaluator mRectFEvaluator = new RectFEvaluator();
     protected Runnable mGestureEndCallback;
     protected boolean mIsGoingToHome;
     private DeviceProfile mDp;
@@ -192,14 +171,9 @@
             InputConsumerController.getRecentsAnimationInputConsumer();
 
     private final RecentsAnimationWrapper mRecentsAnimationWrapper = new RecentsAnimationWrapper();
-    private Matrix mTmpMatrix = new Matrix();
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
 
-    // Only used with the recents activity, when the screenshot should be fetched at the beginning
-    // of the animation and not at the end when the activity is already paused
-    private boolean mSkipScreenshotAtEndOfTransition;
-
     WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs,
             ActivityControlHelper<T> controller) {
         mContext = context;
@@ -273,42 +247,11 @@
 
     private void initTransitionEndpoints(DeviceProfile dp) {
         mDp = dp;
-        mSourceRect.set(mSourceInsets.left, mSourceInsets.top,
-                mSourceStackBounds.width() - mSourceInsets.right,
-                mSourceStackBounds.height() - mSourceInsets.bottom);
 
         Rect tempRect = new Rect();
         mTransitionDragLength = mActivityControlHelper
                 .getSwipeUpDestinationAndLength(dp, mContext, tempRect);
-
-        mTargetRect.set(tempRect);
-        mTargetRect.offset(mHomeStackBounds.left - mSourceStackBounds.left,
-                mHomeStackBounds.top - mSourceStackBounds.top);
-        mInitialTargetRect.set(mTargetRect);
-
-        // Calculate the clip based on the target rect (since the content insets and the
-        // launcher insets may differ, so the aspect ratio of the target rect can differ
-        // from the source rect. The difference between the target rect (scaled to the
-        // source rect) is the amount to clip on each edge.
-        RectF scaledTargetRect = new RectF(mTargetRect);
-        Utilities.scaleRectFAboutCenter(scaledTargetRect,
-                mSourceRect.width() / mTargetRect.width());
-        scaledTargetRect.offsetTo(mSourceRect.left, mSourceRect.top);
-        mSourceWindowClipInsets.set(
-                Math.max(scaledTargetRect.left, 0),
-                Math.max(scaledTargetRect.top, 0),
-                Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
-                Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
-        mSourceRect.set(scaledTargetRect);
-    }
-
-    public int getTransitionLength() {
-        return mTransitionDragLength;
-    }
-
-    public RectF getTargetRect(Point outWindowSize) {
-        outWindowSize.set(mDp.widthPx, mDp.heightPx);
-        return mInitialTargetRect;
+        mClipAnimationHelper.updateTargetRect(tempRect);
     }
 
     private long getFadeInDuration() {
@@ -482,40 +425,10 @@
 
         synchronized (mRecentsAnimationWrapper) {
             if (mRecentsAnimationWrapper.controller != null) {
-                RectF currentRect;
-                synchronized (mTargetRect) {
-                    Interpolator interpolator = mInteractionType == INTERACTION_QUICK_SCRUB
-                            ? ACCEL_2 : LINEAR;
-                    float interpolated = interpolator.getInterpolation(shift);
-                    currentRect = mRectFEvaluator.evaluate(interpolated, mSourceRect, mTargetRect);
-                    // Stay lined up with the center of the target, since it moves for quick scrub.
-                    currentRect.offset(mTargetRect.centerX() - currentRect.centerX(), 0);
-                }
-
-                mClipRect.left = (int) (mSourceWindowClipInsets.left * shift);
-                mClipRect.top = (int) (mSourceWindowClipInsets.top * shift);
-                mClipRect.right = (int)
-                        (mSourceStackBounds.width() - (mSourceWindowClipInsets.right * shift));
-                mClipRect.bottom = (int)
-                        (mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * shift));
-
-                mTmpMatrix.setRectToRect(mSourceRect, currentRect, ScaleToFit.FILL);
-
-                TransactionCompat transaction = new TransactionCompat();
-                for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
-                    if (app.mode == MODE_CLOSING) {
-                        mTmpMatrix.postTranslate(app.position.x, app.position.y);
-                        transaction.setMatrix(app.leash, mTmpMatrix)
-                                .setWindowCrop(app.leash, mClipRect);
-
-                        if (app.isNotInRecents) {
-                            transaction.setAlpha(app.leash, 1 - shift);
-                        }
-
-                        transaction.show(app.leash);
-                    }
-                }
-                transaction.apply();
+                Interpolator interpolator = mInteractionType == INTERACTION_QUICK_SCRUB
+                        ? ACCEL_2 : LINEAR;
+                float interpolated = interpolator.getInterpolation(shift);
+                mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targets, interpolated);
             }
         }
 
@@ -533,13 +446,9 @@
                 if (firstTask != null) {
                     int scrollForFirstTask = mRecentsView.getScrollForPage(0);
                     int offsetFromFirstTask = (scrollForFirstTask - mRecentsView.getScrollX());
-                    synchronized (mTargetRect) {
-                        mTargetRect.set(mInitialTargetRect);
-                        Utilities.scaleRectFAboutCenter(mTargetRect, firstTask.getScaleX());
-                        float offsetX = offsetFromFirstTask + firstTask.getTranslationX();
-                        float offsetY = mRecentsView.getTranslationY();
-                        mTargetRect.offset(offsetX, offsetY);
-                    }
+                    mClipAnimationHelper.offsetTarget(firstTask.getScaleX(),
+                            offsetFromFirstTask + firstTask.getTranslationX(),
+                            mRecentsView.getTranslationY());
                 }
                 if (mRecentsAnimationWrapper.controller != null) {
                     // TODO: This logic is spartanic!
@@ -564,13 +473,15 @@
             for (RemoteAnimationTargetCompat target : apps) {
                 if (target.mode == MODE_CLOSING) {
                     DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext);
+                    final Rect homeStackBounds;
+
                     if (minimizedHomeBounds != null) {
-                        mHomeStackBounds.set(minimizedHomeBounds);
+                        homeStackBounds = minimizedHomeBounds;
                         dp = dp.getMultiWindowProfile(mContext,
                                 new Point(minimizedHomeBounds.width(), minimizedHomeBounds.height()));
                         dp.updateInsets(homeContentInsets);
                     } else {
-                        mHomeStackBounds.set(new Rect(0, 0, dp.widthPx, dp.heightPx));
+                        homeStackBounds = new Rect(0, 0, dp.widthPx, dp.heightPx);
                         // TODO: Workaround for an existing issue where the home content insets are
                         // not valid immediately after rotation, just use the stable insets for now
                         Rect insets = new Rect();
@@ -578,16 +489,7 @@
                         dp.updateInsets(insets);
                     }
 
-                    // Initialize the start and end animation bounds
-                    // TODO: Remove once platform is updated
-                    try {
-                        mSourceInsets.set(target.getContentInsets());
-                    } catch (Error e) {
-                        // TODO: Remove once platform is updated, use stable insets as fallback
-                        WindowManagerWrapper.getInstance().getStableInsets(mSourceInsets);
-                    }
-                    mSourceStackBounds.set(target.sourceContainerBounds);
-
+                    mClipAnimationHelper.updateSource(homeStackBounds, target);
                     initTransitionEndpoints(dp);
                 }
             }
@@ -736,7 +638,7 @@
         };
 
         synchronized (mRecentsAnimationWrapper) {
-            if (mRecentsAnimationWrapper.controller != null && !mSkipScreenshotAtEndOfTransition) {
+            if (mRecentsAnimationWrapper.controller != null) {
                 TransactionCompat transaction = new TransactionCompat();
                 for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
                     if (app.mode == MODE_CLOSING) {
@@ -745,6 +647,7 @@
                                 mRecentsAnimationWrapper.controller.screenshotTask(app.taskId);
                         TaskView taskView = mRecentsView.updateThumbnail(app.taskId, thumbnail);
                         if (taskView != null) {
+                            taskView.setAlpha(1);
                             // Defer finishing the animation until the next launcher frame with the
                             // new thumbnail
                             mActivityControlHelper.executeOnNextDraw(mActivity, taskView,
@@ -763,12 +666,6 @@
         doLogGesture(true /* toLauncher */);
     }
 
-    @UiThread
-    public void switchToScreenshotImmediate(ThumbnailData thumbnail) {
-        mRecentsView.updateThumbnail(mRunningTaskId, thumbnail);
-        mSkipScreenshotAtEndOfTransition = true;
-    }
-
     private void setupLauncherUiAfterSwipeUpAnimation() {
         if (mLauncherTransitionController != null) {
             mLauncherTransitionController.getAnimationPlayer().end();
diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
new file mode 100644
index 0000000..0f895b8
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -0,0 +1,134 @@
+/*
+ * 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.util;
+
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+
+import android.graphics.Matrix;
+import android.graphics.Matrix.ScaleToFit;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import com.android.launcher3.Utilities;
+import com.android.systemui.shared.recents.utilities.RectFEvaluator;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.TransactionCompat;
+
+/**
+ * Utility class to handle window clip animation
+ */
+public class ClipAnimationHelper {
+
+    // The bounds of the source app in device coordinates
+    private final Rect mSourceStackBounds = new Rect();
+    // The insets of the source app
+    private final Rect mSourceInsets = new Rect();
+    // The source app bounds with the source insets applied, in the source app window coordinates
+    private final RectF mSourceRect = new RectF();
+    // The bounds of the task view in launcher window coordinates
+    private final RectF mTargetRect = new RectF();
+    // Doesn't change after initialized, used as an anchor when changing mTargetRect
+    private final RectF mInitialTargetRect = new RectF();
+    // The insets to be used for clipping the app window, which can be larger than mSourceInsets
+    // if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In
+    // app window coordinates.
+    private final RectF mSourceWindowClipInsets = new RectF();
+
+    // The bounds of launcher (not including insets) in device coordinates
+    public final Rect mHomeStackBounds = new Rect();
+
+    // The clip rect in source app window coordinates
+    private final Rect mClipRect = new Rect();
+    private final RectFEvaluator mRectFEvaluator = new RectFEvaluator();
+    private final Matrix mTmpMatrix = new Matrix();
+
+
+    public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) {
+        mHomeStackBounds.set(homeStackBounds);
+        mSourceInsets.set(target.getContentInsets());
+        mSourceStackBounds.set(target.sourceContainerBounds);
+
+        // TODO: Should sourceContainerBounds already have this offset?
+        mSourceStackBounds.offsetTo(target.position.x, target.position.y);
+    }
+
+    public void updateTargetRect(Rect targetRect) {
+        mSourceRect.set(mSourceInsets.left, mSourceInsets.top,
+                mSourceStackBounds.width() - mSourceInsets.right,
+                mSourceStackBounds.height() - mSourceInsets.bottom);
+        mTargetRect.set(targetRect);
+        mTargetRect.offset(mHomeStackBounds.left - mSourceStackBounds.left,
+                mHomeStackBounds.top - mSourceStackBounds.top);
+
+        mInitialTargetRect.set(mTargetRect);
+
+        // Calculate the clip based on the target rect (since the content insets and the
+        // launcher insets may differ, so the aspect ratio of the target rect can differ
+        // from the source rect. The difference between the target rect (scaled to the
+        // source rect) is the amount to clip on each edge.
+        RectF scaledTargetRect = new RectF(mTargetRect);
+        Utilities.scaleRectFAboutCenter(scaledTargetRect,
+                mSourceRect.width() / mTargetRect.width());
+        scaledTargetRect.offsetTo(mSourceRect.left, mSourceRect.top);
+        mSourceWindowClipInsets.set(
+                Math.max(scaledTargetRect.left, 0),
+                Math.max(scaledTargetRect.top, 0),
+                Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
+                Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
+        mSourceRect.set(scaledTargetRect);
+    }
+
+    public void applyTransform(RemoteAnimationTargetCompat[] targets, float progress) {
+        RectF currentRect;
+        synchronized (mTargetRect) {
+            currentRect =  mRectFEvaluator.evaluate(progress, mSourceRect, mTargetRect);
+            // Stay lined up with the center of the target, since it moves for quick scrub.
+            currentRect.offset(mTargetRect.centerX() - currentRect.centerX(), 0);
+        }
+
+        mClipRect.left = (int) (mSourceWindowClipInsets.left * progress);
+        mClipRect.top = (int) (mSourceWindowClipInsets.top * progress);
+        mClipRect.right = (int)
+                (mSourceStackBounds.width() - (mSourceWindowClipInsets.right * progress));
+        mClipRect.bottom = (int)
+                (mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress));
+
+        mTmpMatrix.setRectToRect(mSourceRect, currentRect, ScaleToFit.FILL);
+
+        TransactionCompat transaction = new TransactionCompat();
+        for (RemoteAnimationTargetCompat app : targets) {
+            if (app.mode == MODE_CLOSING) {
+                mTmpMatrix.postTranslate(app.position.x, app.position.y);
+                transaction.setMatrix(app.leash, mTmpMatrix)
+                        .setWindowCrop(app.leash, mClipRect);
+                if (app.isNotInRecents) {
+                    transaction.setAlpha(app.leash, 1 - progress);
+                }
+
+                transaction.show(app.leash);
+            }
+        }
+        transaction.apply();
+    }
+
+    public void offsetTarget(float scale, float offsetX, float offsetY) {
+        synchronized (mTargetRect) {
+            mTargetRect.set(mInitialTargetRect);
+            Utilities.scaleRectFAboutCenter(mTargetRect, scale);
+            mTargetRect.offset(offsetX, offsetY);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
new file mode 100644
index 0000000..2ffcae3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -0,0 +1,55 @@
+/*
+ * 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.util;
+
+import android.animation.AnimatorSet;
+import android.app.ActivityOptions;
+import android.os.Handler;
+
+import com.android.launcher3.LauncherAnimationRunner;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.TransactionCompat;
+
+@FunctionalInterface
+public interface RemoteAnimationProvider {
+
+    AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targets);
+
+    default ActivityOptions toActivityOptions(Handler handler, long duration) {
+        LauncherAnimationRunner runner = new LauncherAnimationRunner(handler) {
+            @Override
+            public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
+                return createWindowAnimation(targetCompats);
+            }
+        };
+        return ActivityOptionsCompat.makeRemoteAnimation(
+                new RemoteAnimationAdapterCompat(runner, duration, 0));
+    }
+
+    static void showOpeningTarget(RemoteAnimationTargetCompat[] targetCompats) {
+        TransactionCompat t = new TransactionCompat();
+        for (RemoteAnimationTargetCompat target : targetCompats) {
+            int layer = target.mode == RemoteAnimationTargetCompat.MODE_CLOSING
+                    ? Integer.MAX_VALUE
+                    : target.prefixOrderIndex;
+            t.setLayer(target.leash, layer);
+            t.show(target.leash);
+        }
+        t.apply();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
index 6b7143d..ac34d90 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep.views;
 
+import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
 import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
 
 import android.graphics.Rect;
@@ -39,6 +40,10 @@
         super(launcher, null);
         mLauncher = launcher;
         setVisibility(INVISIBLE);
+
+        // For the duration of the gesture, lock the screen orientation to ensure that we do not
+        // rotate mid-quickscrub
+        launcher.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index b22e2b6..6997ec4 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -133,13 +133,7 @@
     private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
         public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
-            for (int i = 0; i < getChildCount(); i++) {
-                final TaskView taskView = (TaskView) getChildAt(i);
-                if (taskView.getTask().key.id == taskId) {
-                    taskView.getThumbnail().setThumbnail(taskView.getTask(), snapshot);
-                    return;
-                }
-            }
+            updateThumbnail(taskId, snapshot);
         }
 
         @Override
@@ -241,7 +235,6 @@
             final TaskView taskView = (TaskView) getChildAt(i);
             if (taskView.getTask().key.id == taskId) {
                 taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
-                taskView.setAlpha(1);
                 return taskView;
             }
         }
@@ -604,12 +597,7 @@
                     new ActivityManager.TaskDescription(), 0, new ComponentName("", ""), false);
             taskView.bind(mTmpRunningTask);
         }
-
-        mRunningTaskId = runningTaskId;
-        setCurrentPage(0);
-
-        // Load the tasks (if the loading is already
-        mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
+        setCurrentTask(mRunningTaskId);
 
         // Hide the task that we are animating into, ignore if there is no associated task (ie. the
         // assistant)
@@ -618,6 +606,17 @@
         }
     }
 
+    /**
+     * Similar to {@link #showTask(int)} but does not put any restrictions on the first tile.
+     */
+    public void setCurrentTask(int runningTaskId) {
+        mRunningTaskId = runningTaskId;
+        setCurrentPage(0);
+
+        // Load the tasks (if the loading is already
+        mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
+    }
+
     public void showNextTask() {
         TaskView runningTaskView = getTaskView(mRunningTaskId);
         if (runningTaskView == null) {
@@ -1079,7 +1078,10 @@
     @Override
     protected void notifyPageSwitchListener(int prevPage) {
         super.notifyPageSwitchListener(prevPage);
-        getChildAt(mCurrentPage).sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+        View currChild = getChildAt(mCurrentPage);
+        if (currChild != null) {
+            currChild.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index c04f825..f04acaf 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -148,7 +148,10 @@
         mSnapshotView.setThumbnail(task, thumbnailData);
         mIconView.setImageDrawable(task.icon);
         mIconView.setOnClickListener(icon -> TaskMenuView.showForTask(this));
-        mIconView.setOnLongClickListener(icon -> TaskMenuView.showForTask(this));
+        mIconView.setOnLongClickListener(icon -> {
+            requestDisallowInterceptTouchEvent(true);
+            return TaskMenuView.showForTask(this);
+        });
     }
 
     @Override
diff --git a/res/drawable/ic_close.xml b/res/drawable/ic_close.xml
index fc9ed49..8b2f55f 100644
--- a/res/drawable/ic_close.xml
+++ b/res/drawable/ic_close.xml
@@ -19,5 +19,5 @@
     android:viewportWidth="24.0">
   <path
       android:fillColor="@android:color/white"
-      android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
+      android:pathData="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41z" />
 </vector>
\ No newline at end of file
diff --git a/res/drawable/ic_uninstall_no_shadow.xml b/res/drawable/ic_uninstall_no_shadow.xml
index 2a86e10..37632d1 100644
--- a/res/drawable/ic_uninstall_no_shadow.xml
+++ b/res/drawable/ic_uninstall_no_shadow.xml
@@ -21,6 +21,11 @@
         android:tint="?android:attr/textColorPrimary" >
     <path
         android:fillColor="#FFFFFFFF"
-        android:pathData="M6,19c0,1.1,0.9,2,2,2h8c1.1,0,2-0.9,2-2V7H6V19z M18,4h-2.5l-0.71-0.71C14.61,3.11,14.35,3,14.09,
-        3H9.9C9.64,3,9.38,3.11,9.2,3.29L8.49,4h-2.5c-0.55,0-1,0.45-1,1s0.45,1,1,1h12c0.55,0,1-0.45,1-1C19,4.45,18.55,4,18,4L18,4z"/>
+        android:pathData="M15,4V3H9v1H4v2h1v13c0,1.1,0.9,2,2,2h10c1.1,0,2-0.9,2-2V6h1V4H15z M17,19H7V6h10V19z" />
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M 9 8 H 11 V 17 H 9 V 8 Z" />
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M 13 8 H 15 V 17 H 13 V 8 Z" />
 </vector>
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index bf985c3..4677d31 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -91,6 +91,11 @@
     public static final int FLAG_ADAPTIVE_ICON = 1 << 8;
 
     /**
+     * Flag indicating that the icon is badged.
+     */
+    public static final int FLAG_ICON_BADGED = 1 << 9;
+
+    /**
      * Status associated with the system state of the underlying item. This is calculated every
      * time a new info is created and not persisted on the disk.
      */
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b410f4f..90c55c9 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -294,6 +294,7 @@
         mPopupDataProvider = new PopupDataProvider(this);
 
         mRotationHelper = new RotationHelper(this);
+        mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
 
         boolean internalStateHandled = InternalStateHandler.handleCreate(this, getIntent());
         if (internalStateHandled) {
@@ -341,8 +342,6 @@
         getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
                 Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
 
-        mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
-
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onCreate(savedInstanceState);
         }
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 8d4f2ef..e1e1f83 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -46,6 +46,7 @@
 
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherAppState;
@@ -69,6 +70,8 @@
 import java.util.Arrays;
 import java.util.List;
 
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
+
 public class DragView extends View {
     private static final ColorMatrix sTempMatrix1 = new ColorMatrix();
     private static final ColorMatrix sTempMatrix2 = new ColorMatrix();
@@ -364,7 +367,10 @@
     private Drawable getBadge(ItemInfo info, LauncherAppState appState, Object obj) {
         int iconSize = appState.getInvariantDeviceProfile().iconBitmapSize;
         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-            if (info.id == ItemInfo.NO_ID || !(obj instanceof ShortcutInfoCompat)) {
+            boolean iconBadged = (info instanceof ItemInfoWithIcon)
+                    && (((ItemInfoWithIcon) info).runtimeStatusFlags & FLAG_ICON_BADGED) > 0;
+            if ((info.id == ItemInfo.NO_ID && !iconBadged)
+                    || !(obj instanceof ShortcutInfoCompat)) {
                 // The item is not yet added on home screen.
                 return new FixedSizeEmptyDrawable(iconSize);
             }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 454fc7d..b522b55 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -517,6 +517,12 @@
     }
 
     @Override
+    protected void closeComplete() {
+        super.closeComplete();
+        mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
+    }
+
+    @Override
     public boolean onTouch(View v, MotionEvent ev) {
         // Touched a shortcut, update where it was touched so we can drag from there on long click.
         switch (ev.getAction()) {
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index c875cf9..f1195ed 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -124,6 +124,10 @@
         return mLauncher.getAllAppsController().getShiftRange();
     }
 
+    /**
+     * Returns the state to go to from fromState given the drag direction. If there is no state in
+     * that direction, returns fromState.
+     */
     protected abstract LauncherState getTargetState(LauncherState fromState,
             boolean isDragTowardPositive);
 
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
index c97c3cc..d1cddc1 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
@@ -54,7 +54,12 @@
 
     @Override
     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
-        return fromState == ALL_APPS ? NORMAL : ALL_APPS;
+        if (fromState == NORMAL && isDragTowardPositive) {
+            return ALL_APPS;
+        } else if (fromState == ALL_APPS && !isDragTowardPositive) {
+            return NORMAL;
+        }
+        return fromState;
     }
 
     @Override