Move ActivityControlHelper impl to upper level

This CL moves the implementations of ActivityControlHelper from an inner
class to an upper level class in preparation to override the
implementation-specific logic for Go.

Bug: 114136250
Test: Manual test NexusLauncher, Launcher3QuickStepGo
Change-Id: Ic4b004f57cde263414c8dc57fe5c868dabc00dbe
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index fbb3618..fcb0f6e 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -15,39 +15,15 @@
  */
 package com.android.quickstep;
 
-import static android.view.View.TRANSLATION_Y;
-
-import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_DAMPING_RATIO;
-import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_STIFFNESS;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
-import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
-import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
-import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION;
-
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
 import android.os.Handler;
-import android.os.Looper;
-import android.view.View;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
@@ -56,34 +32,15 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherInitListener;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
-import com.android.launcher3.TestProtocol;
-import com.android.launcher3.allapps.DiscoveryBounce;
-import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.SpringObjectAnimator;
-import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.uioverrides.FastOverviewState;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.quickstep.TouchConsumer.InteractionType;
-import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.util.TransformedRect;
-import com.android.quickstep.views.LauncherLayoutListener;
 import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
-import java.util.Objects;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
@@ -161,597 +118,6 @@
 
     boolean isInLiveTileMode();
 
-    class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
-
-        @Override
-        public LayoutListener createLayoutListener(Launcher activity) {
-            return LauncherLayoutListener.resetAndGet(activity);
-        }
-
-        @Override
-        public void onQuickInteractionStart(Launcher activity, RunningTaskInfo taskInfo,
-                boolean activityVisible, TouchInteractionLog touchInteractionLog) {
-            LauncherState fromState = activity.getStateManager().getState();
-            QuickScrubController controller = activity.<RecentsView>getOverviewPanel()
-                    .getQuickScrubController();
-            boolean isQuickSwitch = controller.isQuickSwitch();
-            boolean animate = activityVisible;
-            if (isQuickSwitch && fromState == FAST_OVERVIEW && !animate) {
-                // We can already be in FAST_OVERVIEW if createActivityController() was called
-                // before us. This could happen, for instance, when launcher is slow to load when
-                // starting quick switch, causing us to call onQuickScrubStart() on the background
-                // thread. In this case, we also hadn't set isQuickSwitch = true before setting
-                // FAST_OVERVIEW, so we need to reapply FAST_OVERVIEW to take that into account.
-                activity.getStateManager().reapplyState();
-            } else {
-                activity.getStateManager().goToState(FAST_OVERVIEW, animate);
-            }
-
-            controller.onQuickScrubStart(activityVisible && !fromState.overviewUi, this,
-                    touchInteractionLog);
-
-            if (!activityVisible) {
-                // For the duration of the gesture, lock the screen orientation to ensure that we
-                // do not rotate mid-quickscrub
-                activity.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
-            }
-        }
-
-        @Override
-        public float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp,
-                Context context) {
-            // The padding calculations are exactly same as that of RecentsView.setInsets
-            int topMargin = context.getResources()
-                    .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
-            int paddingTop = targetRect.rect.top - topMargin - dp.getInsets().top;
-            int paddingBottom = dp.heightPx - dp.getInsets().bottom - targetRect.rect.bottom;
-
-            return FastOverviewState.OVERVIEW_TRANSLATION_FACTOR * (paddingBottom - paddingTop);
-        }
-
-        @Override
-        public void executeOnWindowAvailable(Launcher activity, Runnable action) {
-            activity.getWorkspace().runOnOverlayHidden(action);
-        }
-
-        @Override
-        public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context,
-                @InteractionType int interactionType, TransformedRect outRect) {
-            LayoutUtils.calculateLauncherTaskSize(context, dp, outRect.rect);
-            if (interactionType == INTERACTION_QUICK_SCRUB) {
-                outRect.scale = FastOverviewState.getOverviewScale(dp, outRect.rect, context,
-                        FeatureFlags.QUICK_SWITCH.get());
-            }
-            if (dp.isVerticalBarLayout()) {
-                Rect targetInsets = dp.getInsets();
-                int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
-                return dp.hotseatBarSizePx + hotseatInset;
-            } else {
-                return LayoutUtils.getShelfTrackingDistance(context, dp);
-            }
-        }
-
-        @Override
-        public void onTransitionCancelled(Launcher activity, boolean activityVisible) {
-            LauncherState startState = activity.getStateManager().getRestState();
-            activity.getStateManager().goToState(startState, activityVisible);
-        }
-
-        @Override
-        public void onSwipeUpComplete(Launcher activity) {
-            // Re apply state in case we did something funky during the transition.
-            activity.getStateManager().reapplyState();
-            DiscoveryBounce.showForOverviewIfNeeded(activity);
-        }
-
-        @NonNull
-        @Override
-        public HomeAnimationFactory prepareHomeUI(Launcher activity) {
-            DeviceProfile dp = activity.getDeviceProfile();
-
-            return new HomeAnimationFactory() {
-                @NonNull
-                @Override
-                public RectF getWindowTargetRect() {
-                    int halfIconSize = dp.iconSizePx / 2;
-                    float targetCenterX = dp.availableWidthPx / 2;
-                    float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx;
-                    return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize,
-                            targetCenterX + halfIconSize, targetCenterY + halfIconSize);
-                }
-
-                @NonNull
-                @Override
-                public Animator createActivityAnimationToHome() {
-                    long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
-                    return activity.getStateManager().createAnimationToNewWorkspace(
-                            NORMAL, accuracy).getTarget();
-                }
-            };
-        }
-
-        @Override
-        public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible,
-                boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
-            final LauncherState startState = activity.getStateManager().getState();
-
-            LauncherState resetState = startState;
-            if (startState.disableRestore) {
-                resetState = activity.getStateManager().getRestState();
-            }
-            activity.getStateManager().setRestState(resetState);
-
-            final LauncherState fromState;
-            if (!activityVisible) {
-                // Since the launcher is not visible, we can safely reset the scroll position.
-                // This ensures then the next swipe up to all-apps starts from scroll 0.
-                activity.getAppsView().reset(false /* animate */);
-                fromState = animateActivity ? BACKGROUND_APP : OVERVIEW;
-                activity.getStateManager().goToState(fromState, false);
-
-                // Optimization, hide the all apps view to prevent layout while initializing
-                activity.getAppsView().getContentView().setVisibility(View.GONE);
-
-                AccessibilityManagerCompat.sendEventToTest(
-                        activity, TestProtocol.SWITCHED_TO_STATE_MESSAGE);
-            } else {
-                fromState = startState;
-            }
-
-            return new AnimationFactory() {
-                private Animator mShelfAnim;
-                private ShelfAnimState mShelfState;
-
-                @Override
-                public void createActivityController(long transitionLength,
-                        @InteractionType int interactionType) {
-                    createActivityControllerInternal(activity, activityVisible, fromState,
-                            transitionLength, interactionType, callback);
-                }
-
-                @Override
-                public void onTransitionCancelled() {
-                    activity.getStateManager().goToState(startState, false /* animate */);
-                }
-
-                @Override
-                public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator,
-                        long duration) {
-                    if (mShelfState == shelfState) {
-                        return;
-                    }
-                    mShelfState = shelfState;
-                    if (mShelfAnim != null) {
-                        mShelfAnim.cancel();
-                    }
-                    if (mShelfState == ShelfAnimState.CANCEL) {
-                        return;
-                    }
-                    float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(activity);
-                    float shelfOverviewProgress = OVERVIEW.getVerticalProgress(activity);
-                    float shelfPeekingProgress = shelfHiddenProgress
-                            - (shelfHiddenProgress - shelfOverviewProgress) * 0.25f;
-                    float toProgress = mShelfState == ShelfAnimState.HIDE
-                            ? shelfHiddenProgress
-                            : mShelfState == ShelfAnimState.PEEK
-                                    ? shelfPeekingProgress
-                                    : shelfOverviewProgress;
-                    mShelfAnim = createShelfAnim(activity, toProgress);
-                    mShelfAnim.addListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            mShelfAnim = null;
-                        }
-                    });
-                    mShelfAnim.setInterpolator(interpolator);
-                    mShelfAnim.setDuration(duration);
-                    mShelfAnim.start();
-                }
-            };
-        }
-
-        private void createActivityControllerInternal(Launcher activity, boolean wasVisible,
-                LauncherState fromState, long transitionLength,
-                @InteractionType int interactionType,
-                Consumer<AnimatorPlaybackController> callback) {
-            LauncherState endState = interactionType == INTERACTION_QUICK_SCRUB
-                    ? FAST_OVERVIEW : OVERVIEW;
-            if (wasVisible) {
-                DeviceProfile dp = activity.getDeviceProfile();
-                long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
-                callback.accept(activity.getStateManager()
-                        .createAnimationToNewWorkspace(fromState, endState, accuracy));
-                return;
-            }
-            if (fromState == endState) {
-                return;
-            }
-
-            AnimatorSet anim = new AnimatorSet();
-            if (!activity.getDeviceProfile().isVerticalBarLayout()
-                    && !FeatureFlags.SWIPE_HOME.get()) {
-                // Don't animate the shelf when SWIPE_HOME is true, because we update it atomically.
-                Animator shiftAnim = createShelfAnim(activity,
-                        fromState.getVerticalProgress(activity),
-                        endState.getVerticalProgress(activity));
-                anim.play(shiftAnim);
-            }
-
-            if (interactionType == INTERACTION_NORMAL) {
-                playScaleDownAnim(anim, activity, endState);
-            }
-
-            anim.setDuration(transitionLength * 2);
-            activity.getStateManager().setCurrentAnimation(anim);
-            AnimatorPlaybackController controller =
-                    AnimatorPlaybackController.wrap(anim, transitionLength * 2);
-
-            // Since we are changing the start position of the UI, reapply the state, at the end
-            controller.setEndAction(() -> {
-                activity.getStateManager().goToState(
-                        controller.getInterpolatedProgress() > 0.5 ? endState : fromState, false);
-            });
-            callback.accept(controller);
-        }
-
-        private Animator createShelfAnim(Launcher activity, float ... progressValues) {
-            Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(),
-                    "allAppsSpringFromACH", activity.getAllAppsController().getShiftRange(),
-                    SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
-            shiftAnim.setInterpolator(LINEAR);
-            return shiftAnim;
-        }
-
-        /**
-         * Scale down recents from the center task being full screen to being in overview.
-         */
-        private void playScaleDownAnim(AnimatorSet anim, Launcher launcher,
-                LauncherState endState) {
-            RecentsView recentsView = launcher.getOverviewPanel();
-            TaskView v = recentsView.getTaskViewAt(recentsView.getCurrentPage());
-            if (v == null) {
-                return;
-            }
-
-            // Setup the clip animation helper source/target rects in the final transformed state
-            // of the recents view (a scale may be applied prior to this animation starting to
-            // line up the side pages during swipe up)
-            float prevRvScale = recentsView.getScaleX();
-            float targetRvScale = endState.getOverviewScaleAndTranslationYFactor(launcher)[0];
-            SCALE_PROPERTY.set(recentsView, targetRvScale);
-            ClipAnimationHelper clipHelper = new ClipAnimationHelper(launcher);
-            clipHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(), null);
-            SCALE_PROPERTY.set(recentsView, prevRvScale);
-
-            if (!clipHelper.getSourceRect().isEmpty() && !clipHelper.getTargetRect().isEmpty()) {
-                float fromScale = clipHelper.getSourceRect().width()
-                        / clipHelper.getTargetRect().width();
-                float fromTranslationY = clipHelper.getSourceRect().centerY()
-                        - clipHelper.getTargetRect().centerY();
-                Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, fromScale, 1);
-                Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y,
-                        fromTranslationY, 0);
-                scale.setInterpolator(LINEAR);
-                translateY.setInterpolator(LINEAR);
-                anim.playTogether(scale, translateY);
-            }
-        }
-
-        @Override
-        public ActivityInitListener createActivityInitListener(
-                BiPredicate<Launcher, Boolean> onInitListener) {
-            return new LauncherInitListener(onInitListener);
-        }
-
-        @Nullable
-        @Override
-        public Launcher getCreatedActivity() {
-            LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-            if (app == null) {
-                return null;
-            }
-            return (Launcher) app.getModel().getCallback();
-        }
-
-        @Nullable
-        @UiThread
-        private Launcher getVisibleLaucher() {
-            Launcher launcher = getCreatedActivity();
-            return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ?
-                    launcher : null;
-        }
-
-        @Nullable
-        @Override
-        public RecentsView getVisibleRecentsView() {
-            Launcher launcher = getVisibleLaucher();
-            return launcher != null && launcher.getStateManager().getState().overviewUi
-                    ? launcher.getOverviewPanel() : null;
-        }
-
-        @Override
-        public boolean switchToRecentsIfVisible(boolean fromRecentsButton) {
-            Launcher launcher = getVisibleLaucher();
-            if (launcher != null) {
-                if (fromRecentsButton) {
-                    launcher.getUserEventDispatcher().logActionCommand(
-                            LauncherLogProto.Action.Command.RECENTS_BUTTON,
-                            getContainerType(),
-                            LauncherLogProto.ContainerType.TASKSWITCHER);
-                }
-                launcher.getStateManager().goToState(OVERVIEW);
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        public boolean deferStartingActivity(int downHitTarget) {
-            return downHitTarget == HIT_TARGET_BACK || downHitTarget == HIT_TARGET_ROTATION;
-        }
-
-        @Override
-        public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
-            return homeBounds;
-        }
-
-        @Override
-        public boolean shouldMinimizeSplitScreen() {
-            return true;
-        }
-
-        @Override
-        public boolean supportsLongSwipe(Launcher activity) {
-            return !activity.getDeviceProfile().isVerticalBarLayout();
-        }
-
-        @Override
-        public LongSwipeHelper getLongSwipeController(Launcher activity, int runningTaskId) {
-            if (activity.getDeviceProfile().isVerticalBarLayout()) {
-                return null;
-            }
-            return new LongSwipeHelper(activity, runningTaskId);
-        }
-
-        @Override
-        public AlphaProperty getAlphaProperty(Launcher activity) {
-            return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
-        }
-
-        @Override
-        public int getContainerType() {
-            final Launcher launcher = getVisibleLaucher();
-            return launcher != null ? launcher.getStateManager().getState().containerType
-                    : LauncherLogProto.ContainerType.APP;
-        }
-
-        @Override
-        public boolean isInLiveTileMode() {
-            Launcher launcher = getCreatedActivity();
-            return launcher != null && launcher.getStateManager().getState() == OVERVIEW &&
-                    launcher.isStarted();
-        }
-    }
-
-    class FallbackActivityControllerHelper implements ActivityControlHelper<RecentsActivity> {
-
-        private final ComponentName mHomeComponent;
-        private final Handler mUiHandler = new Handler(Looper.getMainLooper());
-
-        public FallbackActivityControllerHelper(ComponentName homeComponent) {
-            mHomeComponent = homeComponent;
-        }
-
-        @Override
-        public void onQuickInteractionStart(RecentsActivity activity, RunningTaskInfo taskInfo,
-                boolean activityVisible, TouchInteractionLog touchInteractionLog) {
-            QuickScrubController controller = activity.<RecentsView>getOverviewPanel()
-                    .getQuickScrubController();
-
-            // TODO: match user is as well
-            boolean startingFromHome = !activityVisible &&
-                    (taskInfo == null || Objects.equals(taskInfo.topActivity, mHomeComponent));
-            controller.onQuickScrubStart(startingFromHome, this, touchInteractionLog);
-            if (activityVisible) {
-                mUiHandler.postDelayed(controller::onFinishedTransitionToQuickScrub,
-                        OVERVIEW_TRANSITION_MS);
-            }
-        }
-
-        @Override
-        public float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp,
-                Context context) {
-            return 0;
-        }
-
-        @Override
-        public void executeOnWindowAvailable(RecentsActivity activity, Runnable action) {
-            action.run();
-        }
-
-        @Override
-        public void onTransitionCancelled(RecentsActivity activity, boolean activityVisible) {
-            // TODO:
-        }
-
-        @Override
-        public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context,
-                @InteractionType int interactionType, TransformedRect outRect) {
-            LayoutUtils.calculateFallbackTaskSize(context, dp, outRect.rect);
-            if (dp.isVerticalBarLayout()) {
-                Rect targetInsets = dp.getInsets();
-                int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
-                return dp.hotseatBarSizePx + hotseatInset;
-            } else {
-                return dp.heightPx - outRect.rect.bottom;
-            }
-        }
-
-        @Override
-        public void onSwipeUpComplete(RecentsActivity activity) {
-            // TODO:
-        }
-
-        @NonNull
-        @Override
-        public HomeAnimationFactory prepareHomeUI(RecentsActivity activity) {
-            RecentsView recentsView = activity.getOverviewPanel();
-
-            return new HomeAnimationFactory() {
-                @NonNull
-                @Override
-                public RectF getWindowTargetRect() {
-                    float centerX = recentsView.getPivotX();
-                    float centerY = recentsView.getPivotY();
-                    return new RectF(centerX, centerY, centerX, centerY);
-                }
-
-                @NonNull
-                @Override
-                public Animator createActivityAnimationToHome() {
-                    Animator anim = ObjectAnimator.ofFloat(recentsView, CONTENT_ALPHA, 0);
-                    anim.addListener(new AnimationSuccessListener() {
-                        @Override
-                        public void onAnimationSuccess(Animator animator) {
-                            recentsView.startHome();
-                        }
-                    });
-                    return anim;
-                }
-            };
-        }
-
-        @Override
-        public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
-                boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
-            if (activityVisible) {
-                return (transitionLength, interactionType) -> { };
-            }
-
-            RecentsView rv = activity.getOverviewPanel();
-            rv.setContentAlpha(0);
-
-            return new AnimationFactory() {
-
-                boolean isAnimatingToRecents = false;
-
-                @Override
-                public void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) {
-                    isAnimatingToRecents = targets != null && targets.isAnimatingHome();
-                    if (!isAnimatingToRecents) {
-                        rv.setContentAlpha(1);
-                    }
-                    createActivityController(getSwipeUpDestinationAndLength(
-                            activity.getDeviceProfile(), activity, INTERACTION_NORMAL,
-                            new TransformedRect()), INTERACTION_NORMAL);
-                }
-
-                @Override
-                public void createActivityController(long transitionLength, int interactionType) {
-                    if (!isAnimatingToRecents) {
-                        return;
-                    }
-
-                    ObjectAnimator anim = ObjectAnimator.ofFloat(rv, CONTENT_ALPHA, 0, 1);
-                    anim.setDuration(transitionLength).setInterpolator(LINEAR);
-                    AnimatorSet animatorSet = new AnimatorSet();
-                    animatorSet.play(anim);
-                    callback.accept(AnimatorPlaybackController.wrap(animatorSet, transitionLength));
-                }
-            };
-        }
-
-        @Override
-        public LayoutListener createLayoutListener(RecentsActivity activity) {
-            // We do not change anything as part of layout changes in fallback activity. Return a
-            // default layout listener.
-            return new LayoutListener() {
-                @Override
-                public void open() { }
-
-                @Override
-                public void setHandler(WindowTransformSwipeHandler handler) { }
-
-                @Override
-                public void finish() { }
-
-                @Override
-                public void update(boolean shouldFinish, boolean isLongSwipe, RectF currentRect,
-                        float cornerRadius) { }
-            };
-        }
-
-        @Override
-        public ActivityInitListener createActivityInitListener(
-                BiPredicate<RecentsActivity, Boolean> onInitListener) {
-            return new RecentsActivityTracker(onInitListener);
-        }
-
-        @Nullable
-        @Override
-        public RecentsActivity getCreatedActivity() {
-            return RecentsActivityTracker.getCurrentActivity();
-        }
-
-        @Nullable
-        @Override
-        public RecentsView getVisibleRecentsView() {
-            RecentsActivity activity = getCreatedActivity();
-            if (activity != null && activity.hasWindowFocus()) {
-                return activity.getOverviewPanel();
-            }
-            return null;
-        }
-
-        @Override
-        public boolean switchToRecentsIfVisible(boolean fromRecentsButton) {
-            return false;
-        }
-
-        @Override
-        public boolean deferStartingActivity(int downHitTarget) {
-            // Always defer starting the activity when using fallback
-            return true;
-        }
-
-        @Override
-        public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
-            // TODO: Remove this once b/77875376 is fixed
-            return target.sourceContainerBounds;
-        }
-
-        @Override
-        public boolean shouldMinimizeSplitScreen() {
-            // TODO: Remove this once b/77875376 is fixed
-            return false;
-        }
-
-        @Override
-        public boolean supportsLongSwipe(RecentsActivity activity) {
-            return false;
-        }
-
-        @Override
-        public LongSwipeHelper getLongSwipeController(RecentsActivity activity, int runningTaskId) {
-            return null;
-        }
-
-        @Override
-        public AlphaProperty getAlphaProperty(RecentsActivity activity) {
-            return activity.getDragLayer().getAlphaProperty(0);
-        }
-
-        @Override
-        public int getContainerType() {
-            return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
-        }
-
-        @Override
-        public boolean isInLiveTileMode() {
-            return false;
-        }
-    }
-
     interface LayoutListener {
 
         void open();
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
new file mode 100644
index 0000000..aba6060
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2019 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 static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
+import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.quickstep.TouchConsumer.InteractionType;
+import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.TransformedRect;
+import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.util.Objects;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+
+/**
+ * {@link ActivityControlHelper} for recents when the default launcher is different than the
+ * currently running one and apps should interact with the {@link RecentsActivity} as opposed
+ * to the in-launcher one.
+ */
+public final class FallbackActivityControllerHelper implements
+        ActivityControlHelper<RecentsActivity> {
+
+    private final ComponentName mHomeComponent;
+    private final Handler mUiHandler = new Handler(Looper.getMainLooper());
+
+    public FallbackActivityControllerHelper(ComponentName homeComponent) {
+        mHomeComponent = homeComponent;
+    }
+
+    @Override
+    public void onQuickInteractionStart(RecentsActivity activity, RunningTaskInfo taskInfo,
+            boolean activityVisible, TouchInteractionLog touchInteractionLog) {
+        QuickScrubController controller = activity.<RecentsView>getOverviewPanel()
+                .getQuickScrubController();
+
+        // TODO: match user is as well
+        boolean startingFromHome = !activityVisible &&
+                (taskInfo == null || Objects.equals(taskInfo.topActivity, mHomeComponent));
+        controller.onQuickScrubStart(startingFromHome, this, touchInteractionLog);
+        if (activityVisible) {
+            mUiHandler.postDelayed(controller::onFinishedTransitionToQuickScrub,
+                    OVERVIEW_TRANSITION_MS);
+        }
+    }
+
+    @Override
+    public float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp,
+            Context context) {
+        return 0;
+    }
+
+    @Override
+    public void executeOnWindowAvailable(RecentsActivity activity, Runnable action) {
+        action.run();
+    }
+
+    @Override
+    public void onTransitionCancelled(RecentsActivity activity, boolean activityVisible) {
+        // TODO:
+    }
+
+    @Override
+    public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context,
+            @InteractionType int interactionType, TransformedRect outRect) {
+        LayoutUtils.calculateFallbackTaskSize(context, dp, outRect.rect);
+        if (dp.isVerticalBarLayout()) {
+            Rect targetInsets = dp.getInsets();
+            int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
+            return dp.hotseatBarSizePx + hotseatInset;
+        } else {
+            return dp.heightPx - outRect.rect.bottom;
+        }
+    }
+
+    @Override
+    public void onSwipeUpComplete(RecentsActivity activity) {
+        // TODO:
+    }
+
+    @NonNull
+    @Override
+    public HomeAnimationFactory prepareHomeUI(RecentsActivity activity) {
+        RecentsView recentsView = activity.getOverviewPanel();
+
+        return new HomeAnimationFactory() {
+            @NonNull
+            @Override
+            public RectF getWindowTargetRect() {
+                float centerX = recentsView.getPivotX();
+                float centerY = recentsView.getPivotY();
+                return new RectF(centerX, centerY, centerX, centerY);
+            }
+
+            @NonNull
+            @Override
+            public Animator createActivityAnimationToHome() {
+                Animator anim = ObjectAnimator.ofFloat(recentsView, CONTENT_ALPHA, 0);
+                anim.addListener(new AnimationSuccessListener() {
+                    @Override
+                    public void onAnimationSuccess(Animator animator) {
+                        recentsView.startHome();
+                    }
+                });
+                return anim;
+            }
+        };
+    }
+
+    @Override
+    public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
+            boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
+        if (activityVisible) {
+            return (transitionLength, interactionType) -> { };
+        }
+
+        RecentsView rv = activity.getOverviewPanel();
+        rv.setContentAlpha(0);
+
+        return new AnimationFactory() {
+
+            boolean isAnimatingToRecents = false;
+
+            @Override
+            public void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) {
+                isAnimatingToRecents = targets != null && targets.isAnimatingHome();
+                if (!isAnimatingToRecents) {
+                    rv.setContentAlpha(1);
+                }
+                createActivityController(getSwipeUpDestinationAndLength(
+                        activity.getDeviceProfile(), activity, INTERACTION_NORMAL,
+                        new TransformedRect()), INTERACTION_NORMAL);
+            }
+
+            @Override
+            public void createActivityController(long transitionLength, int interactionType) {
+                if (!isAnimatingToRecents) {
+                    return;
+                }
+
+                ObjectAnimator anim = ObjectAnimator.ofFloat(rv, CONTENT_ALPHA, 0, 1);
+                anim.setDuration(transitionLength).setInterpolator(LINEAR);
+                AnimatorSet animatorSet = new AnimatorSet();
+                animatorSet.play(anim);
+                callback.accept(AnimatorPlaybackController.wrap(animatorSet, transitionLength));
+            }
+        };
+    }
+
+    @Override
+    public LayoutListener createLayoutListener(RecentsActivity activity) {
+        // We do not change anything as part of layout changes in fallback activity. Return a
+        // default layout listener.
+        return new LayoutListener() {
+            @Override
+            public void open() { }
+
+            @Override
+            public void setHandler(WindowTransformSwipeHandler handler) { }
+
+            @Override
+            public void finish() { }
+
+            @Override
+            public void update(boolean shouldFinish, boolean isLongSwipe, RectF currentRect,
+                    float cornerRadius) { }
+        };
+    }
+
+    @Override
+    public ActivityInitListener createActivityInitListener(
+            BiPredicate<RecentsActivity, Boolean> onInitListener) {
+        return new RecentsActivityTracker(onInitListener);
+    }
+
+    @Nullable
+    @Override
+    public RecentsActivity getCreatedActivity() {
+        return RecentsActivityTracker.getCurrentActivity();
+    }
+
+    @Nullable
+    @Override
+    public RecentsView getVisibleRecentsView() {
+        RecentsActivity activity = getCreatedActivity();
+        if (activity != null && activity.hasWindowFocus()) {
+            return activity.getOverviewPanel();
+        }
+        return null;
+    }
+
+    @Override
+    public boolean switchToRecentsIfVisible(boolean fromRecentsButton) {
+        return false;
+    }
+
+    @Override
+    public boolean deferStartingActivity(int downHitTarget) {
+        // Always defer starting the activity when using fallback
+        return true;
+    }
+
+    @Override
+    public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
+        // TODO: Remove this once b/77875376 is fixed
+        return target.sourceContainerBounds;
+    }
+
+    @Override
+    public boolean shouldMinimizeSplitScreen() {
+        // TODO: Remove this once b/77875376 is fixed
+        return false;
+    }
+
+    @Override
+    public boolean supportsLongSwipe(RecentsActivity activity) {
+        return false;
+    }
+
+    @Override
+    public LongSwipeHelper getLongSwipeController(RecentsActivity activity, int runningTaskId) {
+        return null;
+    }
+
+    @Override
+    public AlphaProperty getAlphaProperty(RecentsActivity activity) {
+        return activity.getDragLayer().getAlphaProperty(0);
+    }
+
+    @Override
+    public int getContainerType() {
+        return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
+    }
+
+    @Override
+    public boolean isInLiveTileMode() {
+        return false;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
new file mode 100644
index 0000000..e8cc6fb
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2019 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 static android.view.View.TRANSLATION_Y;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_DAMPING_RATIO;
+import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_STIFFNESS;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
+import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
+import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherInitListener;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.TestProtocol;
+import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.SpringObjectAnimator;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.uioverrides.FastOverviewState;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.quickstep.TouchConsumer.InteractionType;
+import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.TransformedRect;
+import com.android.quickstep.views.LauncherLayoutListener;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+
+/**
+ * {@link ActivityControlHelper} for the in-launcher recents.
+ */
+public final class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
+
+    @Override
+    public LayoutListener createLayoutListener(Launcher activity) {
+        return LauncherLayoutListener.resetAndGet(activity);
+    }
+
+    @Override
+    public void onQuickInteractionStart(Launcher activity, RunningTaskInfo taskInfo,
+            boolean activityVisible, TouchInteractionLog touchInteractionLog) {
+        LauncherState fromState = activity.getStateManager().getState();
+        QuickScrubController controller = activity.<RecentsView>getOverviewPanel()
+                .getQuickScrubController();
+        boolean isQuickSwitch = controller.isQuickSwitch();
+        boolean animate = activityVisible;
+        if (isQuickSwitch && fromState == FAST_OVERVIEW && !animate) {
+            // We can already be in FAST_OVERVIEW if createActivityController() was called
+            // before us. This could happen, for instance, when launcher is slow to load when
+            // starting quick switch, causing us to call onQuickScrubStart() on the background
+            // thread. In this case, we also hadn't set isQuickSwitch = true before setting
+            // FAST_OVERVIEW, so we need to reapply FAST_OVERVIEW to take that into account.
+            activity.getStateManager().reapplyState();
+        } else {
+            activity.getStateManager().goToState(FAST_OVERVIEW, animate);
+        }
+
+        controller.onQuickScrubStart(activityVisible && !fromState.overviewUi, this,
+                touchInteractionLog);
+
+        if (!activityVisible) {
+            // For the duration of the gesture, lock the screen orientation to ensure that we
+            // do not rotate mid-quickscrub
+            activity.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
+        }
+    }
+
+    @Override
+    public float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp,
+            Context context) {
+        // The padding calculations are exactly same as that of RecentsView.setInsets
+        int topMargin = context.getResources()
+                .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+        int paddingTop = targetRect.rect.top - topMargin - dp.getInsets().top;
+        int paddingBottom = dp.heightPx - dp.getInsets().bottom - targetRect.rect.bottom;
+
+        return FastOverviewState.OVERVIEW_TRANSLATION_FACTOR * (paddingBottom - paddingTop);
+    }
+
+    @Override
+    public void executeOnWindowAvailable(Launcher activity, Runnable action) {
+        activity.getWorkspace().runOnOverlayHidden(action);
+    }
+
+    @Override
+    public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context,
+            @InteractionType int interactionType, TransformedRect outRect) {
+        LayoutUtils.calculateLauncherTaskSize(context, dp, outRect.rect);
+        if (interactionType == INTERACTION_QUICK_SCRUB) {
+            outRect.scale = FastOverviewState.getOverviewScale(dp, outRect.rect, context,
+                    FeatureFlags.QUICK_SWITCH.get());
+        }
+        if (dp.isVerticalBarLayout()) {
+            Rect targetInsets = dp.getInsets();
+            int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
+            return dp.hotseatBarSizePx + hotseatInset;
+        } else {
+            return LayoutUtils.getShelfTrackingDistance(context, dp);
+        }
+    }
+
+    @Override
+    public void onTransitionCancelled(Launcher activity, boolean activityVisible) {
+        LauncherState startState = activity.getStateManager().getRestState();
+        activity.getStateManager().goToState(startState, activityVisible);
+    }
+
+    @Override
+    public void onSwipeUpComplete(Launcher activity) {
+        // Re apply state in case we did something funky during the transition.
+        activity.getStateManager().reapplyState();
+        DiscoveryBounce.showForOverviewIfNeeded(activity);
+    }
+
+    @NonNull
+    @Override
+    public HomeAnimationFactory prepareHomeUI(Launcher activity) {
+        DeviceProfile dp = activity.getDeviceProfile();
+
+        return new HomeAnimationFactory() {
+            @NonNull
+            @Override
+            public RectF getWindowTargetRect() {
+                int halfIconSize = dp.iconSizePx / 2;
+                float targetCenterX = dp.availableWidthPx / 2;
+                float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx;
+                return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize,
+                        targetCenterX + halfIconSize, targetCenterY + halfIconSize);
+            }
+
+            @NonNull
+            @Override
+            public Animator createActivityAnimationToHome() {
+                long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
+                return activity.getStateManager().createAnimationToNewWorkspace(
+                        NORMAL, accuracy).getTarget();
+            }
+        };
+    }
+
+    @Override
+    public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible,
+            boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
+        final LauncherState startState = activity.getStateManager().getState();
+
+        LauncherState resetState = startState;
+        if (startState.disableRestore) {
+            resetState = activity.getStateManager().getRestState();
+        }
+        activity.getStateManager().setRestState(resetState);
+
+        final LauncherState fromState;
+        if (!activityVisible) {
+            // Since the launcher is not visible, we can safely reset the scroll position.
+            // This ensures then the next swipe up to all-apps starts from scroll 0.
+            activity.getAppsView().reset(false /* animate */);
+            fromState = animateActivity ? BACKGROUND_APP : OVERVIEW;
+            activity.getStateManager().goToState(fromState, false);
+
+            // Optimization, hide the all apps view to prevent layout while initializing
+            activity.getAppsView().getContentView().setVisibility(View.GONE);
+
+            AccessibilityManagerCompat.sendEventToTest(
+                    activity, TestProtocol.SWITCHED_TO_STATE_MESSAGE);
+        } else {
+            fromState = startState;
+        }
+
+        return new AnimationFactory() {
+            private Animator mShelfAnim;
+            private ShelfAnimState mShelfState;
+
+            @Override
+            public void createActivityController(long transitionLength,
+                    @InteractionType int interactionType) {
+                createActivityControllerInternal(activity, activityVisible, fromState,
+                        transitionLength, interactionType, callback);
+            }
+
+            @Override
+            public void onTransitionCancelled() {
+                activity.getStateManager().goToState(startState, false /* animate */);
+            }
+
+            @Override
+            public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator,
+                    long duration) {
+                if (mShelfState == shelfState) {
+                    return;
+                }
+                mShelfState = shelfState;
+                if (mShelfAnim != null) {
+                    mShelfAnim.cancel();
+                }
+                if (mShelfState == ShelfAnimState.CANCEL) {
+                    return;
+                }
+                float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(activity);
+                float shelfOverviewProgress = OVERVIEW.getVerticalProgress(activity);
+                float shelfPeekingProgress = shelfHiddenProgress
+                        - (shelfHiddenProgress - shelfOverviewProgress) * 0.25f;
+                float toProgress = mShelfState == ShelfAnimState.HIDE
+                        ? shelfHiddenProgress
+                        : mShelfState == ShelfAnimState.PEEK
+                                ? shelfPeekingProgress
+                                : shelfOverviewProgress;
+                mShelfAnim = createShelfAnim(activity, toProgress);
+                mShelfAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mShelfAnim = null;
+                    }
+                });
+                mShelfAnim.setInterpolator(interpolator);
+                mShelfAnim.setDuration(duration);
+                mShelfAnim.start();
+            }
+        };
+    }
+
+    private void createActivityControllerInternal(Launcher activity, boolean wasVisible,
+            LauncherState fromState, long transitionLength,
+            @InteractionType int interactionType,
+            Consumer<AnimatorPlaybackController> callback) {
+        LauncherState endState = interactionType == INTERACTION_QUICK_SCRUB
+                ? FAST_OVERVIEW : OVERVIEW;
+        if (wasVisible) {
+            DeviceProfile dp = activity.getDeviceProfile();
+            long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
+            callback.accept(activity.getStateManager()
+                    .createAnimationToNewWorkspace(fromState, endState, accuracy));
+            return;
+        }
+        if (fromState == endState) {
+            return;
+        }
+
+        AnimatorSet anim = new AnimatorSet();
+        if (!activity.getDeviceProfile().isVerticalBarLayout()
+                && !FeatureFlags.SWIPE_HOME.get()) {
+            // Don't animate the shelf when SWIPE_HOME is true, because we update it atomically.
+            Animator shiftAnim = createShelfAnim(activity,
+                    fromState.getVerticalProgress(activity),
+                    endState.getVerticalProgress(activity));
+            anim.play(shiftAnim);
+        }
+
+        if (interactionType == INTERACTION_NORMAL) {
+            playScaleDownAnim(anim, activity, endState);
+        }
+
+        anim.setDuration(transitionLength * 2);
+        activity.getStateManager().setCurrentAnimation(anim);
+        AnimatorPlaybackController controller =
+                AnimatorPlaybackController.wrap(anim, transitionLength * 2);
+
+        // Since we are changing the start position of the UI, reapply the state, at the end
+        controller.setEndAction(() -> {
+            activity.getStateManager().goToState(
+                    controller.getInterpolatedProgress() > 0.5 ? endState : fromState, false);
+        });
+        callback.accept(controller);
+    }
+
+    private Animator createShelfAnim(Launcher activity, float ... progressValues) {
+        Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(),
+                "allAppsSpringFromACH", activity.getAllAppsController().getShiftRange(),
+                SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
+        shiftAnim.setInterpolator(LINEAR);
+        return shiftAnim;
+    }
+
+    /**
+     * Scale down recents from the center task being full screen to being in overview.
+     */
+    private void playScaleDownAnim(AnimatorSet anim, Launcher launcher,
+            LauncherState endState) {
+        RecentsView recentsView = launcher.getOverviewPanel();
+        TaskView v = recentsView.getTaskViewAt(recentsView.getCurrentPage());
+        if (v == null) {
+            return;
+        }
+
+        // Setup the clip animation helper source/target rects in the final transformed state
+        // of the recents view (a scale may be applied prior to this animation starting to
+        // line up the side pages during swipe up)
+        float prevRvScale = recentsView.getScaleX();
+        float targetRvScale = endState.getOverviewScaleAndTranslationYFactor(launcher)[0];
+        SCALE_PROPERTY.set(recentsView, targetRvScale);
+        ClipAnimationHelper clipHelper = new ClipAnimationHelper(launcher);
+        clipHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(), null);
+        SCALE_PROPERTY.set(recentsView, prevRvScale);
+
+        if (!clipHelper.getSourceRect().isEmpty() && !clipHelper.getTargetRect().isEmpty()) {
+            float fromScale = clipHelper.getSourceRect().width()
+                    / clipHelper.getTargetRect().width();
+            float fromTranslationY = clipHelper.getSourceRect().centerY()
+                    - clipHelper.getTargetRect().centerY();
+            Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, fromScale, 1);
+            Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y,
+                    fromTranslationY, 0);
+            scale.setInterpolator(LINEAR);
+            translateY.setInterpolator(LINEAR);
+            anim.playTogether(scale, translateY);
+        }
+    }
+
+    @Override
+    public ActivityInitListener createActivityInitListener(
+            BiPredicate<Launcher, Boolean> onInitListener) {
+        return new LauncherInitListener(onInitListener);
+    }
+
+    @Nullable
+    @Override
+    public Launcher getCreatedActivity() {
+        LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+        if (app == null) {
+            return null;
+        }
+        return (Launcher) app.getModel().getCallback();
+    }
+
+    @Nullable
+    @UiThread
+    private Launcher getVisibleLaucher() {
+        Launcher launcher = getCreatedActivity();
+        return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ?
+                launcher : null;
+    }
+
+    @Nullable
+    @Override
+    public RecentsView getVisibleRecentsView() {
+        Launcher launcher = getVisibleLaucher();
+        return launcher != null && launcher.getStateManager().getState().overviewUi
+                ? launcher.getOverviewPanel() : null;
+    }
+
+    @Override
+    public boolean switchToRecentsIfVisible(boolean fromRecentsButton) {
+        Launcher launcher = getVisibleLaucher();
+        if (launcher != null) {
+            if (fromRecentsButton) {
+                launcher.getUserEventDispatcher().logActionCommand(
+                        LauncherLogProto.Action.Command.RECENTS_BUTTON,
+                        getContainerType(),
+                        LauncherLogProto.ContainerType.TASKSWITCHER);
+            }
+            launcher.getStateManager().goToState(OVERVIEW);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean deferStartingActivity(int downHitTarget) {
+        return downHitTarget == HIT_TARGET_BACK || downHitTarget == HIT_TARGET_ROTATION;
+    }
+
+    @Override
+    public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
+        return homeBounds;
+    }
+
+    @Override
+    public boolean shouldMinimizeSplitScreen() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsLongSwipe(Launcher activity) {
+        return !activity.getDeviceProfile().isVerticalBarLayout();
+    }
+
+    @Override
+    public LongSwipeHelper getLongSwipeController(Launcher activity, int runningTaskId) {
+        if (activity.getDeviceProfile().isVerticalBarLayout()) {
+            return null;
+        }
+        return new LongSwipeHelper(activity, runningTaskId);
+    }
+
+    @Override
+    public AlphaProperty getAlphaProperty(Launcher activity) {
+        return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
+    }
+
+    @Override
+    public int getContainerType() {
+        final Launcher launcher = getVisibleLaucher();
+        return launcher != null ? launcher.getStateManager().getState().containerType
+                : LauncherLogProto.ContainerType.APP;
+    }
+
+    @Override
+    public boolean isInLiveTileMode() {
+        Launcher launcher = getCreatedActivity();
+        return launcher != null && launcher.getStateManager().getState() == OVERVIEW &&
+                launcher.isStarted();
+    }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index e119e53..b6741f4 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -31,8 +31,6 @@
 import android.content.pm.ResolveInfo;
 import android.os.PatternMatcher;
 
-import com.android.quickstep.ActivityControlHelper.FallbackActivityControllerHelper;
-import com.android.quickstep.ActivityControlHelper.LauncherActivityControllerHelper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
 import java.util.ArrayList;