Merge "Disabling unnecessary requestLayout calls in BubbleTextView" into ub-launcher3-master
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 4a26494..8b28597 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -47,7 +47,8 @@
         <!-- STOPSHIP: Change exported to false once all the integration is complete.
         It is set to true so that the activity can be started from command line -->
         <activity android:name="com.android.quickstep.RecentsActivity"
-            android:exported="true" />
+            android:exported="true"
+            android:excludeFromRecents="true" />
 
         <!-- Content provider to settings search -->
         <provider
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 33987e2..6e56055 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 243395d..8e66526 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -807,9 +807,7 @@
                                          Runnable finishedCallback) {
                 Handler handler = mLauncher.getWindow().getDecorView().getHandler();
                 postAtFrontOfQueueAsynchronously(handler, () -> {
-                    if ((Utilities.getPrefs(mLauncher)
-                            .getBoolean("pref_use_screenshot_for_swipe_up", false)
-                            && mLauncher.getStateManager().getState().overviewUi)) {
+                    if (mLauncher.getStateManager().getState().overviewUi) {
                         // We use a separate transition for Overview mode.
                         setCurrentAnimator(null);
                         finishedCallback.run();
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
new file mode 100644
index 0000000..0d1038a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3;
+
+import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import com.android.launcher3.states.InternalStateHandler;
+import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+
+import java.util.function.BiPredicate;
+
+@TargetApi(Build.VERSION_CODES.P)
+public class LauncherInitListener extends InternalStateHandler implements ActivityInitListener {
+
+    private final BiPredicate<Launcher, Boolean> mOnInitListener;
+
+    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);
+        return mOnInitListener.test(launcher, alreadyOnHome);
+    }
+
+    @Override
+    public void register() {
+        initWhenReady();
+    }
+
+    @Override
+    public void unregister() {
+        clearReference();
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 789185a..637ce60 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 
+import android.content.Context;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
 
@@ -73,7 +74,8 @@
                 }
             }
         }
-        OverviewInteractionState.setBackButtonVisible(launcher, shouldBackButtonBeVisible);
+        OverviewInteractionState.getInstance(launcher)
+                .setBackButtonVisible(shouldBackButtonBeVisible);
     }
 
     public static void resetOverview(Launcher launcher) {
@@ -81,15 +83,15 @@
         recents.reset();
     }
 
-    public static void onStart(Launcher launcher) {
-        RecentsModel model = RecentsModel.getInstance(launcher);
+    public static void onStart(Context context) {
+        RecentsModel model = RecentsModel.getInstance(context);
         if (model != null) {
             model.onStart();
         }
     }
 
-    public static void onTrimMemory(Launcher launcher, int level) {
-        RecentsModel model = RecentsModel.getInstance(launcher);
+    public static void onTrimMemory(Context context, int level) {
+        RecentsModel model = RecentsModel.getInstance(context);
         if (model != null) {
             model.onTrimMemory(level);
         }
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
new file mode 100644
index 0000000..a9da4f9
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -0,0 +1,308 @@
+/*
+ * 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 static com.android.launcher3.LauncherState.FAST_OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherInitListener;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.util.ViewOnDrawExecutor;
+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;
+
+/**
+ * Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
+ */
+public interface ActivityControlHelper<T extends BaseDraggingActivity> {
+
+    LayoutListener createLayoutListener(T activity);
+
+    void onQuickstepGestureStarted(T activity, boolean activityVisible);
+
+    void onQuickInteractionStart(T activity, boolean activityVisible);
+
+    void executeOnNextDraw(T activity, TaskView targetView, Runnable action);
+
+    void onTransitionCancelled(T activity, boolean activityVisible);
+
+    int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect);
+
+    void onSwipeUpComplete(T activity);
+
+    void prepareRecentsUI(T activity, boolean activityVisible);
+
+    AnimatorPlaybackController createControllerForVisibleActivity(T activity);
+
+    AnimatorPlaybackController createControllerForHiddenActivity(T activity, int transitionLength);
+
+    ActivityInitListener createActivityInitListener(BiPredicate<T, Boolean> onInitListener);
+
+    void startRecents(Context context, Intent intent, AssistDataReceiver assistDataReceiver,
+            RecentsAnimationListener remoteAnimationListener);
+
+    class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
+
+        @Override
+        public LayoutListener createLayoutListener(Launcher activity) {
+            return new LauncherLayoutListener(activity);
+        }
+
+        @Override
+        public void onQuickstepGestureStarted(Launcher activity, boolean activityVisible) {
+            activity.onQuickstepGestureStarted(activityVisible);
+        }
+
+        @Override
+        public void onQuickInteractionStart(Launcher activity, boolean activityVisible) {
+            activity.getStateManager().goToState(FAST_OVERVIEW, activityVisible);
+        }
+
+        @Override
+        public void executeOnNextDraw(Launcher activity, TaskView targetView, Runnable action) {
+            ViewOnDrawExecutor executor = new ViewOnDrawExecutor() {
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                    if (!isCompleted()) {
+                        runAllTasks();
+                    }
+                }
+            };
+            executor.attachTo(activity, targetView, false /* waitForLoadAnimation */);
+            executor.execute(action);
+        }
+
+        @Override
+        public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
+            RecentsView.getPageRect(dp, context, outRect);
+            if (dp.isVerticalBarLayout()) {
+                Rect targetInsets = dp.getInsets();
+                int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
+                return dp.hotseatBarSizePx + dp.hotseatBarSidePaddingPx + hotseatInset;
+            } else {
+                return dp.heightPx - outRect.bottom;
+            }
+        }
+
+        @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();
+        }
+
+        @Override
+        public void prepareRecentsUI(Launcher activity, boolean activityVisible) {
+            LauncherState startState = activity.getStateManager().getState();
+            if (startState.disableRestore) {
+                startState = activity.getStateManager().getRestState();
+            }
+            activity.getStateManager().setRestState(startState);
+
+            if (!activityVisible) {
+                activity.getStateManager().goToState(OVERVIEW, false);
+
+                // Optimization, hide the all apps view to prevent layout while initializing
+                activity.getAppsView().setVisibility(View.GONE);
+            }
+        }
+
+        @Override
+        public AnimatorPlaybackController createControllerForVisibleActivity(Launcher activity) {
+            DeviceProfile dp = activity.getDeviceProfile();
+            long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
+            return activity.getStateManager().createAnimationToNewWorkspace(OVERVIEW, accuracy);
+        }
+
+        @Override
+        public AnimatorPlaybackController createControllerForHiddenActivity(
+                Launcher activity, int transitionLength) {
+            float startProgress;
+            AllAppsTransitionController controller = activity.getAllAppsController();
+
+            if (activity.getDeviceProfile().isVerticalBarLayout()) {
+                startProgress = 1;
+            } else {
+                float scrollRange = Math.max(controller.getShiftRange(), 1);
+                startProgress = (transitionLength / scrollRange) + 1;
+            }
+            AnimatorSet anim = new AnimatorSet();
+            ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(controller, ALL_APPS_PROGRESS,
+                    startProgress, OVERVIEW.getVerticalProgress(activity));
+            shiftAnim.setInterpolator(LINEAR);
+            anim.play(shiftAnim);
+
+            // TODO: Link this animation to state animation, so that it is cancelled
+            // automatically on state change
+            anim.setDuration(transitionLength * 2);
+            return AnimatorPlaybackController.wrap(anim, transitionLength * 2);
+        }
+
+        @Override
+        public ActivityInitListener createActivityInitListener(
+                BiPredicate<Launcher, Boolean> onInitListener) {
+            return new LauncherInitListener(onInitListener);
+        }
+
+        @Override
+        public void startRecents(Context context, Intent intent,
+                AssistDataReceiver assistDataReceiver,
+                RecentsAnimationListener remoteAnimationListener) {
+            ActivityManagerWrapper.getInstance().startRecentsActivity(
+                    intent, assistDataReceiver, remoteAnimationListener, null, null);
+        }
+    }
+
+    class FallbackActivityControllerHelper implements ActivityControlHelper<RecentsActivity> {
+
+        @Override
+        public void onQuickstepGestureStarted(RecentsActivity activity, boolean activityVisible) {
+            // TODO:
+        }
+
+        @Override
+        public void onQuickInteractionStart(RecentsActivity activity, boolean activityVisible) {
+            // TODO:
+        }
+
+        @Override
+        public void executeOnNextDraw(RecentsActivity activity, TaskView targetView,
+                Runnable action) {
+            // TODO:
+            new Handler(Looper.getMainLooper()).post(action);
+        }
+
+        @Override
+        public void onTransitionCancelled(RecentsActivity activity, boolean activityVisible) {
+            // TODO:
+        }
+
+        @Override
+        public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
+            FallbackRecentsView.getCenterPageRect(dp, context, outRect);
+            if (dp.isVerticalBarLayout()) {
+                Rect targetInsets = dp.getInsets();
+                int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
+                return dp.hotseatBarSizePx + dp.hotseatBarSidePaddingPx + hotseatInset;
+            } else {
+                return dp.heightPx - outRect.bottom;
+            }
+        }
+
+        @Override
+        public void onSwipeUpComplete(RecentsActivity activity) {
+            // TODO:
+        }
+
+        @Override
+        public void prepareRecentsUI(RecentsActivity activity, boolean activityVisible) {
+            // TODO:
+        }
+
+        @Override
+        public AnimatorPlaybackController createControllerForVisibleActivity(
+                RecentsActivity activity) {
+            DeviceProfile dp = activity.getDeviceProfile();
+            return createControllerForHiddenActivity(activity, Math.max(dp.widthPx, dp.heightPx));
+        }
+
+        @Override
+        public AnimatorPlaybackController createControllerForHiddenActivity(
+                RecentsActivity activity, int transitionLength) {
+            // We do not animate anything. Create a empty controller
+            AnimatorSet anim = new AnimatorSet();
+            return AnimatorPlaybackController.wrap(anim, transitionLength * 2);
+        }
+
+        @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 ActivityInitListener createActivityInitListener(
+                BiPredicate<RecentsActivity, Boolean> onInitListener) {
+            return new RecentsActivityTracker(onInitListener);
+        }
+
+        @Override
+        public void startRecents(Context context, Intent intent,
+                AssistDataReceiver assistDataReceiver,
+                final RecentsAnimationListener remoteAnimationListener) {
+            ActivityOptions options =
+                    ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
+                            new FallbackActivityOptions(remoteAnimationListener), 10000, 10000));
+            context.startActivity(intent, options.toBundle());
+        }
+    }
+
+    interface LayoutListener {
+
+        void open();
+
+        void setHandler(WindowTransformSwipeHandler handler);
+
+        void finish();
+    }
+
+    interface ActivityInitListener {
+
+        void register();
+
+        void unregister();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
deleted file mode 100644
index 5871a6d..0000000
--- a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
+++ /dev/null
@@ -1,50 +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.support.annotation.WorkerThread;
-
-import com.android.launcher3.states.InternalStateHandler;
-import com.android.quickstep.TouchConsumer.InteractionType;
-
-public abstract class BaseSwipeInteractionHandler extends InternalStateHandler {
-
-    protected Runnable mGestureEndCallback;
-    protected boolean mIsGoingToHome;
-
-    public void setGestureEndCallback(Runnable gestureEndCallback) {
-        mGestureEndCallback = gestureEndCallback;
-    }
-
-    public void reset() {}
-
-    @WorkerThread
-    public abstract void onGestureStarted();
-
-    @WorkerThread
-    public abstract void onGestureEnded(float endVelocity);
-
-    public abstract void updateInteractionType(@InteractionType int interactionType);
-
-    @WorkerThread
-    public abstract void onQuickScrubEnd();
-
-    @WorkerThread
-    public abstract void onQuickScrubProgress(float progress);
-
-    @WorkerThread
-    public abstract void updateDisplacement(float displacement);
-}
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityOptions.java b/quickstep/src/com/android/quickstep/FallbackActivityOptions.java
new file mode 100644
index 0000000..3a7fb2d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/FallbackActivityOptions.java
@@ -0,0 +1,82 @@
+/*
+ * 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 android.util.Log;
+
+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.WindowManagerWrapper;
+
+/**
+ * Temporary class to create activity options to emulate recents transition for fallback activtiy.
+ */
+public class FallbackActivityOptions implements RemoteAnimationRunnerCompat {
+
+    private final RecentsAnimationListener mListener;
+
+    public FallbackActivityOptions(RecentsAnimationListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats,
+            Runnable runnable) {
+        DummyRecentsAnimationControllerCompat dummyRecentsAnim =
+                new DummyRecentsAnimationControllerCompat(runnable);
+
+        Rect insets = new Rect();
+        WindowManagerWrapper.getInstance().getStableInsets(insets);
+        mListener.onAnimationStart(dummyRecentsAnim, targetCompats, insets, null);
+    }
+
+    @Override
+    public void onAnimationCancelled() {
+        mListener.onAnimationCanceled();
+    }
+
+    private static class DummyRecentsAnimationControllerCompat
+            extends RecentsAnimationControllerCompat {
+
+        final Runnable mFinishCallback;
+
+        public DummyRecentsAnimationControllerCompat(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) {
+            if (toHome) {
+                mFinishCallback.run();
+            }
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/FallbackRecentsView.java
index 22f6e0c..032d753 100644
--- a/quickstep/src/com/android/quickstep/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/FallbackRecentsView.java
@@ -48,10 +48,21 @@
         setPadding(padding.left, padding.top, padding.right, padding.bottom);
     }
 
-    public static void verticalCenter(Rect padding, DeviceProfile dp) {
+    private static void verticalCenter(Rect padding, DeviceProfile dp) {
         Rect insets = dp.getInsets();
         int totalSpace = (padding.top + padding.bottom - insets.top - insets.bottom) / 2;
         padding.top = insets.top + totalSpace;
         padding.bottom = insets.bottom + totalSpace;
     }
+
+    public static void getCenterPageRect(DeviceProfile grid, Context context, Rect outRect) {
+        Rect targetPadding = getPadding(grid, context);
+        verticalCenter(targetPadding, grid);
+        Rect insets = grid.getInsets();
+        outRect.set(
+                targetPadding.left + insets.left,
+                targetPadding.top + insets.top,
+                grid.widthPx - targetPadding.right - insets.right,
+                grid.heightPx - targetPadding.bottom - insets.bottom);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
deleted file mode 100644
index 89c9d16..0000000
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ /dev/null
@@ -1,394 +0,0 @@
-/*
- * Copyright (C) 2017 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.quickstep.TouchConsumer.INTERACTION_NORMAL;
-import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SWITCH;
-import static com.android.quickstep.TouchConsumer.isInteractionQuick;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.animation.RectEvaluator;
-import android.annotation.TargetApi;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.os.Build;
-import android.support.annotation.UiThread;
-import android.view.View;
-import android.view.ViewTreeObserver.OnPreDrawListener;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Hotseat;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.Launcher.OnResumeCallback;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.TraceHelper;
-import com.android.quickstep.TouchConsumer.InteractionType;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-
-@TargetApi(Build.VERSION_CODES.O)
-public class NavBarSwipeInteractionHandler extends BaseSwipeInteractionHandler implements
-        OnResumeCallback {
-
-    private static final int STATE_LAUNCHER_READY = 1 << 0;
-    private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE = 1 << 4;
-    private static final int STATE_SCALED_SNAPSHOT_RECENTS = 1 << 5;
-    private static final int STATE_SCALED_SNAPSHOT_APP = 1 << 6;
-
-    private static final long MAX_SWIPE_DURATION = 200;
-    private static final long MIN_SWIPE_DURATION = 80;
-
-    // Ideal velocity for a smooth transition
-    private static final float PIXEL_PER_MS = 2f;
-
-    private static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
-
-    private final Rect mStableInsets = new Rect();
-    private final Rect mSourceRect = new Rect();
-    private final Rect mTargetRect = new Rect();
-    private final Rect mCurrentRect = new Rect();
-    private final RectEvaluator mRectEvaluator = new RectEvaluator(mCurrentRect);
-
-    // Shift in the range of [0, 1].
-    // 0 => preview snapShot is completely visible, and hotseat is completely translated down
-    // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
-    // visible.
-    private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
-
-    // Activity multiplier in the range of [0, 1]. When the activity becomes visible, this is
-    // animated to 1, so allow for a smooth transition.
-    private final AnimatedFloat mActivityMultiplier = new AnimatedFloat(this::updateFinalShift);
-
-    private final int mRunningTaskId;
-    private final Context mContext;
-
-    private final MultiStateCallback mStateCallback;
-
-    private Launcher mLauncher;
-    private SnapshotDragView mDragView;
-    private RecentsView mRecentsView;
-    private QuickScrubController mQuickScrubController;
-    private Hotseat mHotseat;
-
-    private boolean mWasLauncherAlreadyVisible;
-
-    private boolean mLauncherReady;
-    private boolean mTouchEndHandled;
-    private float mCurrentDisplacement;
-
-    private @InteractionType int mInteractionType;
-    private boolean mStartedQuickScrubFromHome;
-
-    private Bitmap mTaskSnapshot;
-
-    NavBarSwipeInteractionHandler(RunningTaskInfo runningTaskInfo, Context context,
-            @InteractionType int interactionType) {
-        mContext = context;
-        mInteractionType = interactionType;
-        mRunningTaskId = runningTaskInfo.id;
-        WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
-
-        DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext);
-        // TODO: If in multi window mode, dp = dp.getMultiWindowProfile()
-        dp = dp.copy(mContext);
-        // TODO: Use different insets for multi-window mode
-        dp.updateInsets(mStableInsets);
-        RecentsView.getPageRect(dp, mContext, mTargetRect);
-        mSourceRect.set(0, 0, dp.widthPx - mStableInsets.left - mStableInsets.right,
-                dp.heightPx - mStableInsets.top - mStableInsets.bottom);
-
-        // Build the state callback
-        mStateCallback = new MultiStateCallback();
-        mStateCallback.addCallback(STATE_LAUNCHER_READY, this::onLauncherReady);
-        mStateCallback.addCallback(STATE_SCALED_SNAPSHOT_APP, this::resumeLastTask);
-        mStateCallback.addCallback(
-                STATE_SCALED_SNAPSHOT_RECENTS | STATE_ACTIVITY_MULTIPLIER_COMPLETE,
-                this::onAnimationToLauncherComplete);
-        mStateCallback.addCallback(STATE_LAUNCHER_READY | STATE_SCALED_SNAPSHOT_APP,
-                this::cleanupLauncher);
-    }
-
-    private void onLauncherReady() {
-        mLauncherReady = true;
-        executeFrameUpdate();
-
-        long duration = Math.min(MAX_SWIPE_DURATION,
-                Math.max((long) (-mCurrentDisplacement / PIXEL_PER_MS), MIN_SWIPE_DURATION));
-        if (mCurrentShift.getCurrentAnimation() != null) {
-            ObjectAnimator anim = mCurrentShift.getCurrentAnimation();
-            long theirDuration = anim.getDuration() - anim.getCurrentPlayTime();
-
-            // TODO: Find a better heuristic
-            duration = (duration + theirDuration) / 2;
-        }
-        ObjectAnimator anim = mActivityMultiplier.animateToValue(1)
-                .setDuration(duration);
-        anim.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
-            }
-        });
-        anim.start();
-    }
-
-    public void setTaskSnapshot(Bitmap taskSnapshot) {
-        mTaskSnapshot = taskSnapshot;
-    }
-
-    @Override
-    public void onLauncherResume() {
-        TraceHelper.partitionSection("TouchInt", "Launcher On resume");
-        mDragView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
-            @Override
-            public boolean onPreDraw() {
-                mDragView.getViewTreeObserver().removeOnPreDrawListener(this);
-                mStateCallback.setState(STATE_LAUNCHER_READY);
-                TraceHelper.partitionSection("TouchInt", "Launcher drawn");
-                return true;
-            }
-        });
-    }
-
-    @Override
-    protected boolean init(Launcher launcher, boolean alreadyOnHome) {
-        launcher.setOnResumeCallback(this);
-        mLauncher = launcher;
-        mRecentsView = launcher.getOverviewPanel();
-        mRecentsView.showTask(mRunningTaskId);
-        mHotseat = mLauncher.getHotseat();
-        mWasLauncherAlreadyVisible = alreadyOnHome;
-
-        AbstractFloatingView.closeAllOpenViews(mLauncher, alreadyOnHome);
-        mLauncher.getStateManager().goToState(LauncherState.OVERVIEW, alreadyOnHome);
-
-        mDragView = new SnapshotDragView(mLauncher, mTaskSnapshot);
-        mLauncher.getDragLayer().addView(mDragView);
-        mDragView.setPivotX(0);
-        mDragView.setPivotY(0);
-
-        if (isInteractionQuick(mInteractionType)) {
-            updateUiForQuickScrub();
-        }
-
-        // Optimization
-        if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-            // All-apps search box is visible in vertical bar layout.
-            mLauncher.getAppsView().setVisibility(View.GONE);
-        }
-        TraceHelper.partitionSection("TouchInt", "Launcher on new intent");
-        return false;
-    }
-
-    public void updateInteractionType(@InteractionType int interactionType) {
-        Preconditions.assertUIThread();
-        if (mInteractionType != INTERACTION_NORMAL) {
-            throw new IllegalArgumentException(
-                    "Can't change interaction type from " + mInteractionType);
-        }
-        if (!isInteractionQuick(interactionType)) {
-            throw new IllegalArgumentException(
-                    "Can't change interaction type to " + interactionType);
-        }
-        mInteractionType = interactionType;
-
-        if (mLauncher != null) {
-            updateUiForQuickScrub();
-        }
-    }
-
-    private void updateUiForQuickScrub() {
-        mStartedQuickScrubFromHome = mWasLauncherAlreadyVisible;
-        mQuickScrubController = mRecentsView.getQuickScrubController();
-        mQuickScrubController.onQuickScrubStart(mStartedQuickScrubFromHome);
-        animateToProgress(1f, MAX_SWIPE_DURATION);
-        if (mStartedQuickScrubFromHome) {
-            mDragView.setVisibility(View.INVISIBLE);
-        }
-    }
-
-    @UiThread
-    public void updateDisplacement(float displacement) {
-        mCurrentDisplacement = displacement;
-        executeFrameUpdate();
-    }
-
-    private void executeFrameUpdate() {
-        if (mLauncherReady) {
-            final float displacement = -mCurrentDisplacement;
-            int hotseatSize = getHotseatSize();
-            float translation = Utilities.boundToRange(displacement, 0, hotseatSize);
-            float shift = hotseatSize == 0 ? 0 : translation / hotseatSize;
-            mCurrentShift.updateValue(shift);
-        }
-    }
-
-    @UiThread
-    private void updateFinalShift() {
-        if (!mLauncherReady || mStartedQuickScrubFromHome) {
-            return;
-        }
-
-        float shift = mCurrentShift.value * mActivityMultiplier.value;
-
-        AllAppsTransitionController controller = mLauncher.getAllAppsController();
-        float range = getHotseatSize() / controller.getShiftRange();
-        controller.setProgress(1 + (1 - shift) * range);
-
-        mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect);
-
-        float scale = (float) mCurrentRect.width() / mSourceRect.width();
-        mDragView.setTranslationX(mCurrentRect.left - mStableInsets.left * scale * shift);
-        mDragView.setTranslationY(mCurrentRect.top - mStableInsets.top * scale * shift);
-        mDragView.setScaleX(scale);
-        mDragView.setScaleY(scale);
-        //  TODO: mDragView.getViewBounds().setClipLeft((int) (mStableInsets.left * shift));
-        mDragView.getViewBounds().setClipTop((int) (mStableInsets.top * shift));
-        // TODO: mDragView.getViewBounds().setClipRight((int) (mStableInsets.right * shift));
-        mDragView.getViewBounds().setClipBottom((int) (mStableInsets.bottom * shift));
-    }
-
-    private int getHotseatSize() {
-        return mLauncher.getDeviceProfile().isVerticalBarLayout()
-                ? mHotseat.getWidth() : mHotseat.getHeight();
-    }
-
-    @Override
-    public void onGestureStarted() { }
-
-    @UiThread
-    public void onGestureEnded(float endVelocity) {
-        if (mTouchEndHandled) {
-            return;
-        }
-        mTouchEndHandled = true;
-
-        Resources res = mContext.getResources();
-        float flingThreshold = res.getDimension(R.dimen.quickstep_fling_threshold_velocity);
-        boolean isFling = Math.abs(endVelocity) > flingThreshold;
-
-        long duration = MAX_SWIPE_DURATION;
-        final float endShift;
-        if (!isFling) {
-            endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? 1 : 0;
-        } else {
-            endShift = endVelocity < 0 ? 1 : 0;
-            float minFlingVelocity = res.getDimension(R.dimen.quickstep_fling_min_velocity);
-            if (Math.abs(endVelocity) > minFlingVelocity && mLauncherReady) {
-                float distanceToTravel = (endShift - mCurrentShift.value) * getHotseatSize();
-
-                // we want the page's snap velocity to approximately match the velocity at
-                // which the user flings, so we scale the duration by a value near to the
-                // derivative of the scroll interpolator at zero, ie. 5.
-                duration = 5 * Math.round(1000 * Math.abs(distanceToTravel / endVelocity));
-            }
-        }
-
-        animateToProgress(endShift, duration);
-    }
-
-    /** Animates to the given progress, where 0 is the current app and 1 is overview. */
-    private void animateToProgress(float progress, long duration) {
-        mIsGoingToHome = Float.compare(progress, 1) == 0;
-        ObjectAnimator anim = mCurrentShift.animateToValue(progress).setDuration(duration);
-        anim.setInterpolator(Interpolators.SCROLL);
-        anim.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                mStateCallback.setState(mIsGoingToHome
-                        ? STATE_SCALED_SNAPSHOT_RECENTS : STATE_SCALED_SNAPSHOT_APP);
-            }
-        });
-        anim.start();
-    }
-
-    @UiThread
-    private void resumeLastTask() {
-        RecentsTaskLoadPlan loadPlan = RecentsModel.getInstance(mContext).getLastLoadPlan();
-        if (loadPlan != null) {
-            Task task = loadPlan.getTaskStack().findTaskWithId(mRunningTaskId);
-            if (task != null) {
-                ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
-                ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(task.key, opts,
-                        null, null);
-            }
-        }
-    }
-
-    public void reset() {
-        mCurrentShift.cancelAnimation();
-        if (mGestureEndCallback != null) {
-            mGestureEndCallback.run();
-        }
-    }
-
-    private void cleanupLauncher() {
-        reset();
-
-        // TODO: These should be done as part of ActivityOptions#OnAnimationStarted
-        mLauncher.getStateManager().reapplyState();
-        mLauncher.setOnResumeCallback(() -> mDragView.close(false));
-    }
-
-    private void onAnimationToLauncherComplete() {
-        reset();
-
-        mDragView.close(false);
-        View currentRecentsPage = mRecentsView.getPageAt(mRecentsView.getCurrentPage());
-        if (currentRecentsPage instanceof TaskView) {
-            ((TaskView) currentRecentsPage).animateIconToScale(1f);
-        }
-        if (mInteractionType == INTERACTION_QUICK_SWITCH) {
-            if (mQuickScrubController != null) {
-                mQuickScrubController.onQuickSwitch();
-            }
-        }
-    }
-
-    public void onQuickScrubEnd() {
-        if (mQuickScrubController != null) {
-            mQuickScrubController.onQuickScrubEnd();
-        } else {
-            // TODO:
-        }
-    }
-
-    public void onQuickScrubProgress(float progress) {
-        if (mQuickScrubController != null) {
-            mQuickScrubController.onQuickScrubProgress(progress);
-        } else {
-            // TODO:
-        }
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 93e4cc9..ab19c6e 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -22,21 +22,14 @@
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.INVALID_POINTER_ID;
 
-import static com.android.quickstep.RemoteRunnable.executeSafely;
-import static com.android.quickstep.TouchInteractionService.DEBUG_SHOW_OVERVIEW_BUTTON;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Color;
-import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Build;
@@ -52,9 +45,7 @@
 import android.view.WindowManager;
 
 import com.android.launcher3.MainThreadExecutor;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.util.TraceHelper;
-import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.AssistDataReceiver;
 import com.android.systemui.shared.system.BackgroundExecutor;
@@ -73,16 +64,15 @@
  */
 @TargetApi(Build.VERSION_CODES.P)
 public class OtherActivityTouchConsumer extends ContextWrapper implements TouchConsumer {
-    private static final String TAG = "ActivityTouchConsumer";
 
     private static final long LAUNCHER_DRAW_TIMEOUT_MS = 150;
-    private static final int[] DEFERRED_HIT_TARGETS = DEBUG_SHOW_OVERVIEW_BUTTON
+    private static final int[] DEFERRED_HIT_TARGETS = false
             ? new int[] {HIT_TARGET_BACK, HIT_TARGET_OVERVIEW} : new int[] {HIT_TARGET_BACK};
 
     private final RunningTaskInfo mRunningTask;
     private final RecentsModel mRecentsModel;
     private final Intent mHomeIntent;
-    private final ISystemUiProxy mISystemUiProxy;
+    private final ActivityControlHelper mActivityControlHelper;
     private final MainThreadExecutor mMainThreadExecutor;
     private final Choreographer mBackgroundThreadChoreographer;
 
@@ -93,7 +83,7 @@
     private boolean mTouchThresholdCrossed;
     private int mTouchSlop;
     private float mStartDisplacement;
-    private BaseSwipeInteractionHandler mInteractionHandler;
+    private WindowTransformSwipeHandler mInteractionHandler;
     private int mDisplayRotation;
     private Rect mStableInsets = new Rect();
 
@@ -102,7 +92,7 @@
     private boolean mIsGoingToHome;
 
     public OtherActivityTouchConsumer(Context base, RunningTaskInfo runningTaskInfo,
-            RecentsModel recentsModel, Intent homeIntent, ISystemUiProxy systemUiProxy,
+            RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
             MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer,
             @HitTarget int downHitTarget, VelocityTracker velocityTracker) {
         super(base);
@@ -110,7 +100,7 @@
         mRecentsModel = recentsModel;
         mHomeIntent = homeIntent;
         mVelocityTracker = velocityTracker;
-        mISystemUiProxy = systemUiProxy;
+        mActivityControlHelper = activityControl;
         mMainThreadExecutor = mainThreadExecutor;
         mBackgroundThreadChoreographer = backgroundThreadChoreographer;
         mIsDeferredDownTarget = Arrays.binarySearch(DEFERRED_HIT_TARGETS, downHitTarget) >= 0;
@@ -132,7 +122,7 @@
 
                 // Start the window animation on down to give more time for launcher to draw if the
                 // user didn't start the gesture over the back button
-                if (!isUsingScreenShot() && !mIsDeferredDownTarget) {
+                if (!mIsDeferredDownTarget) {
                     startTouchTrackingForWindowAnimation(ev.getEventTime());
                 }
 
@@ -172,14 +162,11 @@
                     if (mTouchThresholdCrossed) {
                         mStartDisplacement = Math.signum(displacement) * mTouchSlop;
 
-                        if (isUsingScreenShot()) {
-                            startTouchTrackingForScreenshotAnimation();
-                        } else if (mIsDeferredDownTarget) {
+                        if (mIsDeferredDownTarget) {
                             // If we deferred starting the window animation on touch down, then
                             // start tracking now
                             startTouchTrackingForWindowAnimation(ev.getEventTime());
                         }
-
                         notifyGestureStarted();
                     }
                 } else if (mInteractionHandler != null) {
@@ -215,69 +202,10 @@
         return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0;
     }
 
-    private boolean isUsingScreenShot() {
-        return Utilities.getPrefs(this).getBoolean("pref_use_screenshot_for_swipe_up", false);
-    }
-
-    /**
-     * Called when the gesture has started.
-     */
-    private void startTouchTrackingForScreenshotAnimation() {
-        // Create the shared handler
-        final NavBarSwipeInteractionHandler handler =
-                new NavBarSwipeInteractionHandler(mRunningTask, this, INTERACTION_NORMAL);
-
-        TraceHelper.partitionSection("TouchInt", "Thershold crossed ");
-
-        // Start the recents activity on a background thread
-        BackgroundExecutor.get().submit(() -> {
-            // Get the snap shot before
-            handler.setTaskSnapshot(getCurrentTaskSnapshot());
-
-            // Start the launcher activity with our custom handler
-            Intent homeIntent = handler.addToIntent(new Intent(mHomeIntent));
-            startActivity(homeIntent, ActivityOptions.makeCustomAnimation(this, 0, 0).toBundle());
-            TraceHelper.partitionSection("TouchInt", "Home started");
-        });
-
-        // Preload the plan
-        mRecentsModel.loadTasks(mRunningTask.id, null);
-        mInteractionHandler = handler;
-        mInteractionHandler.setGestureEndCallback(mEventQueue::reset);
-    }
-
-    private Bitmap getCurrentTaskSnapshot() {
-        TraceHelper.beginSection("TaskSnapshot");
-        // TODO: We are using some hardcoded layers for now, to best approximate the activity layers
-        Point displaySize = new Point();
-        Display display = getSystemService(WindowManager.class).getDefaultDisplay();
-        display.getRealSize(displaySize);
-        int rotation = display.getRotation();
-        // The rotation is backwards in landscape, so flip it.
-        if (rotation == Surface.ROTATION_270) {
-            rotation = Surface.ROTATION_90;
-        } else if (rotation == Surface.ROTATION_90) {
-            rotation = Surface.ROTATION_270;
-        }
-        try {
-            return mISystemUiProxy.screenshot(new Rect(), displaySize.x, displaySize.y, 0, 100000,
-                    false, rotation).toBitmap();
-        } catch (Exception e) {
-            Log.e(TAG, "Error capturing snapshot", e);
-
-            // Return a dummy bitmap
-            Bitmap bitmap = Bitmap.createBitmap(displaySize.x, displaySize.y, Config.RGB_565);
-            bitmap.eraseColor(Color.WHITE);
-            return bitmap;
-        } finally {
-            TraceHelper.endSection("TaskSnapshot");
-        }
-    }
-
     private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
         // Create the shared handler
-        final WindowTransformSwipeHandler handler =
-                new WindowTransformSwipeHandler(mRunningTask, this, touchTimeMs);
+        final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
+                mRunningTask, this, touchTimeMs, mActivityControlHelper);
 
         // Preload the plan
         mRecentsModel.loadTasks(mRunningTask.id, null);
@@ -294,8 +222,7 @@
         handler.initWhenReady();
 
         TraceHelper.beginSection("RecentsController");
-        Runnable startActivity = () -> ActivityManagerWrapper.getInstance()
-                .startRecentsActivity(mHomeIntent,
+        Runnable startActivity = () -> mActivityControlHelper.startRecents(this, mHomeIntent,
                 new AssistDataReceiver() {
                     @Override
                     public void onHandleAssistData(Bundle bundle) {
@@ -324,7 +251,7 @@
                             handler.onRecentsAnimationCanceled();
                         }
                     }
-                }, null, null);
+                });
 
         if (Looper.myLooper() != Looper.getMainLooper()) {
             startActivity.run();
@@ -353,7 +280,7 @@
                     : isNavBarOnLeft() ? -mVelocityTracker.getXVelocity(mActivePointerId)
                             : mVelocityTracker.getYVelocity(mActivePointerId);
             mInteractionHandler.onGestureEnded(velocity);
-        } else if (!isUsingScreenShot()) {
+        } else {
             // Since we start touch tracking on DOWN, we may reach this state without actually
             // starting the gesture. In that case, just cleanup immediately.
             reset();
@@ -370,7 +297,7 @@
     public void reset() {
         // Clean up the old interaction handler
         if (mInteractionHandler != null) {
-            final BaseSwipeInteractionHandler handler = mInteractionHandler;
+            final WindowTransformSwipeHandler handler = mInteractionHandler;
             mInteractionHandler = null;
             mIsGoingToHome = handler.mIsGoingToHome;
             mMainThreadExecutor.execute(handler::reset);
@@ -380,24 +307,15 @@
     @Override
     public void updateTouchTracking(int interactionType) {
         notifyGestureStarted();
-
-        if (isUsingScreenShot()) {
-            mMainThreadExecutor.execute(() -> {
-                if (mInteractionHandler != null) {
-                    mInteractionHandler.updateInteractionType(interactionType);
-                }
-            });
-        } else {
-            if (mInteractionHandler != null) {
-                mInteractionHandler.updateInteractionType(interactionType);
-            }
+        if (mInteractionHandler != null) {
+            mInteractionHandler.updateInteractionType(interactionType);
         }
     }
 
     @Override
     public Choreographer getIntrimChoreographer(MotionEventQueue queue) {
         mEventQueue = queue;
-        return isUsingScreenShot() ? null : mBackgroundThreadChoreographer;
+        return mBackgroundThreadChoreographer;
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index efca9eb..38c25a3 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -16,7 +16,6 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.quickstep.TouchInteractionService.DEBUG_SHOW_OVERVIEW_BUTTON;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RecentTaskInfo;
@@ -33,6 +32,8 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.states.InternalStateHandler;
+import com.android.quickstep.ActivityControlHelper.FallbackActivityControllerHelper;
+import com.android.quickstep.ActivityControlHelper.LauncherActivityControllerHelper;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 
@@ -42,7 +43,7 @@
 @TargetApi(Build.VERSION_CODES.P)
 public class OverviewCommandHelper extends InternalStateHandler {
 
-    private static final boolean DEBUG_START_FALLBACK_ACTIVITY = DEBUG_SHOW_OVERVIEW_BUTTON;
+    private static final boolean DEBUG_START_FALLBACK_ACTIVITY = false;
 
     private final Context mContext;
     private final ActivityManagerWrapper mAM;
@@ -61,7 +62,15 @@
                 .setPackage(context.getPackageName())
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         ResolveInfo info = context.getPackageManager().resolveActivity(homeIntent, 0);
-        launcher = new ComponentName(context.getPackageName(), info.activityInfo.name);
+
+        if (DEBUG_START_FALLBACK_ACTIVITY) {
+            launcher = new ComponentName(context, RecentsActivity.class);
+            homeIntent.addCategory(Intent.CATEGORY_DEFAULT)
+                    .removeCategory(Intent.CATEGORY_HOME);
+        } else {
+            launcher = new ComponentName(context.getPackageName(), info.activityInfo.name);
+        }
+
         // Clear the packageName as system can fail to dedupe it b/64108432
         homeIntent.setComponent(launcher).setPackage(null);
     }
@@ -73,35 +82,45 @@
     }
 
     public void onOverviewToggle() {
-        if (DEBUG_START_FALLBACK_ACTIVITY) {
-            mContext.startActivity(new Intent(mContext, RecentsActivity.class)
-                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
-            return;
-        }
+        getLauncher().runOnUiThread(() -> {
+                    if (isUsingFallbackActivity()) {
+                        mContext.startActivity(new Intent(mContext, RecentsActivity.class)
+                                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent
+                                        .FLAG_ACTIVITY_CLEAR_TASK));
+                        return;
+                    }
 
-        long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
-        mLastToggleTime = SystemClock.elapsedRealtime();
+                    long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
+                    mLastToggleTime = SystemClock.elapsedRealtime();
 
-        if (isOverviewAlmostVisible()) {
-            boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
-            startNonLauncherTask(isQuickTap ? 2 : 1);
-        } else {
-            openRecents();
-        }
+                    if (isOverviewAlmostVisible()) {
+                        boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
+                        startNonLauncherTask(isQuickTap ? 2 : 1);
+                    } else {
+                        openRecents();
+                    }
+                }
+        );
     }
 
     public void onOverviewShown() {
-        if (isOverviewAlmostVisible()) {
-            final RecentsView rv = getLauncher().getOverviewPanel();
-            rv.snapToTaskAfterNext();
-        } else {
-            openRecents();
-        }
+        getLauncher().runOnUiThread(() -> {
+                    if (isOverviewAlmostVisible()) {
+                        final RecentsView rv = getLauncher().getOverviewPanel();
+                        rv.snapToTaskAfterNext();
+                    } else {
+                        openRecents();
+                    }
+                }
+        );
     }
 
     public void onOverviewHidden() {
-        final RecentsView rv = getLauncher().getOverviewPanel();
-        rv.launchNextTask();
+        getLauncher().runOnUiThread(() -> {
+                    final RecentsView rv = getLauncher().getOverviewPanel();
+                    rv.launchNextTask();
+                }
+        );
     }
 
     private void startNonLauncherTask(int backStackCount) {
@@ -137,4 +156,15 @@
         return false;
     }
 
+    public boolean isUsingFallbackActivity() {
+        return DEBUG_START_FALLBACK_ACTIVITY;
+    }
+
+    public ActivityControlHelper getActivityControlHelper() {
+        if (DEBUG_START_FALLBACK_ACTIVITY) {
+            return new FallbackActivityControllerHelper();
+        } else {
+            return new LauncherActivityControllerHelper();
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 4af89bf..522a883 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,20 +15,28 @@
  */
 package com.android.quickstep;
 
-import static com.android.quickstep.TouchInteractionService.DEBUG_SHOW_OVERVIEW_BUTTON;
+import static com.android.launcher3.Utilities.getPrefs;
+import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
+import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP;
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_HIDE_BACK_BUTTON;
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
 
 import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.support.annotation.WorkerThread;
 import android.util.Log;
 
+import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
+import java.util.concurrent.ExecutionException;
+
 /**
  * Sets overview interaction flags, such as:
  *
@@ -39,55 +47,109 @@
  *
  * @see com.android.systemui.shared.system.NavigationBarCompat.InteractionType and associated flags.
  */
-public class OverviewInteractionState {
+public class OverviewInteractionState implements OnSharedPreferenceChangeListener {
 
     private static final String TAG = "OverviewFlags";
-    private static final Handler sUiHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            updateOverviewInteractionFlag((Context) msg.obj, msg.what, msg.arg1 == 1);
-        }
-    };
-    private static final Handler sBackgroundHandler = new Handler(
-            UiThreadHelper.getBackgroundLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            ISystemUiProxy systemUiProxy = (ISystemUiProxy) msg.obj;
-            int flags = msg.what;
-            try {
-                systemUiProxy.setInteractionState(flags);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Unable to update overview interaction flags", e);
+
+    // We do not need any synchronization for this variable as its only written on UI thread.
+    private static OverviewInteractionState INSTANCE;
+
+    public static OverviewInteractionState getInstance(final Context context) {
+        if (INSTANCE == null) {
+            if (Looper.myLooper() == Looper.getMainLooper()) {
+                INSTANCE = new OverviewInteractionState(context.getApplicationContext());
+            } else {
+                try {
+                    return new MainThreadExecutor().submit(
+                            () -> OverviewInteractionState.getInstance(context)).get();
+                } catch (InterruptedException|ExecutionException e) {
+                    throw new RuntimeException(e);
+                }
             }
         }
-    };
-
-    private static int sFlags = DEBUG_SHOW_OVERVIEW_BUTTON ? FLAG_SHOW_OVERVIEW_BUTTON : 0;
-
-    public static void setBackButtonVisible(Context context, boolean visible) {
-        updateFlagOnUi(context, FLAG_HIDE_BACK_BUTTON, !visible);
+        return INSTANCE;
     }
 
-    private static void updateFlagOnUi(Context context, int flag, boolean enabled) {
-        sUiHandler.removeMessages(flag);
-        sUiHandler.sendMessage(sUiHandler.obtainMessage(flag, enabled ? 1 : 0, 0, context));
+    private static final String KEY_SWIPE_UP_ENABLED = "pref_enable_quickstep";
+
+    private static final int MSG_SET_PROXY = 200;
+    private static final int MSG_SET_BACK_BUTTON_VISIBLE = 201;
+    private static final int MSG_SET_SWIPE_UP_ENABLED = 202;
+
+    private final Handler mUiHandler;
+    private final Handler mBgHandler;
+
+    // These are updated on the background thread
+    private ISystemUiProxy mISystemUiProxy;
+    private boolean mBackButtonVisible = true;
+    private boolean mSwipeUpEnabled = true;
+
+    private OverviewInteractionState(Context context) {
+        mUiHandler = new Handler(this::handleUiMessage);
+        mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
+
+        SharedPreferences prefs = getPrefs(context);
+        prefs.registerOnSharedPreferenceChangeListener(this);
+        onSharedPreferenceChanged(prefs, KEY_SWIPE_UP_ENABLED);
     }
 
-    private static void updateOverviewInteractionFlag(Context context, int flag, boolean enabled) {
-        if (enabled) {
-            sFlags |= flag;
-        } else {
-            sFlags &= ~flag;
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences prefs, String s) {
+        if (KEY_SWIPE_UP_ENABLED.equals(s)) {
+            mUiHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED);
+            boolean swipeUpEnabled = prefs.getBoolean(s, true);
+            mUiHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED,
+                    swipeUpEnabled ? 1 : 0, 0).sendToTarget();
         }
+    }
 
-        ISystemUiProxy systemUiProxy = RecentsModel.getInstance(context).getSystemUiProxy();
-        if (systemUiProxy == null) {
-            Log.w(TAG, "Unable to update overview interaction flags; not bound to service");
+    public void setBackButtonVisible(boolean visible) {
+        mUiHandler.removeMessages(MSG_SET_BACK_BUTTON_VISIBLE);
+        mUiHandler.obtainMessage(MSG_SET_BACK_BUTTON_VISIBLE, visible ? 1 : 0, 0)
+                .sendToTarget();
+    }
+
+    public void setSystemUiProxy(ISystemUiProxy proxy) {
+        mBgHandler.obtainMessage(MSG_SET_PROXY, proxy).sendToTarget();
+    }
+
+    private boolean handleUiMessage(Message msg) {
+        mBgHandler.obtainMessage(msg.what, msg.arg1, msg.arg2).sendToTarget();
+        return true;
+    }
+
+    private boolean handleBgMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_SET_PROXY:
+                mISystemUiProxy = (ISystemUiProxy) msg.obj;
+                break;
+            case MSG_SET_BACK_BUTTON_VISIBLE:
+                mBackButtonVisible = msg.arg1 != 0;
+                break;
+            case MSG_SET_SWIPE_UP_ENABLED:
+                mSwipeUpEnabled = msg.arg1 != 0;
+                break;
+        }
+        applyFlags();
+        return true;
+    }
+
+    @WorkerThread
+    private void applyFlags() {
+        if (mISystemUiProxy == null) {
             return;
         }
-        // If we aren't already setting these flags, do so now on the background thread.
-        if (!sBackgroundHandler.hasMessages(sFlags)) {
-            sBackgroundHandler.sendMessage(sBackgroundHandler.obtainMessage(sFlags, systemUiProxy));
+
+        int flags;
+        if (mSwipeUpEnabled) {
+            flags = mBackButtonVisible ? 0 : FLAG_HIDE_BACK_BUTTON;
+        } else {
+            flags = FLAG_DISABLE_SWIPE_UP | FLAG_DISABLE_QUICK_SCRUB | FLAG_SHOW_OVERVIEW_BUTTON;
+        }
+        try {
+            mISystemUiProxy.setInteractionState(flags);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Unable to update overview interaction flags", e);
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index a154b29..d263fbf 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -84,7 +84,10 @@
         }
         int page = mRecentsView.getNextPage();
         Runnable launchTaskRunnable = () -> {
-            ((TaskView) mRecentsView.getPageAt(page)).launchTask(true);
+            TaskView taskView = ((TaskView) mRecentsView.getPageAt(page));
+            if (taskView != null) {
+                taskView.launchTask(true);
+            }
         };
         int snapDuration = Math.abs(page - mRecentsView.getPageNearestToCenterOfScreen())
                 * QUICKSCRUB_END_SNAP_DURATION_PER_PAGE;
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 1d443fd..12e1a2b 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -25,6 +25,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.badge.BadgeInfo;
+import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.views.BaseDragLayer;
 
 /**
@@ -49,6 +50,8 @@
         setContentView(R.layout.fallback_recents_activity);
         mRecentsRootView = findViewById(R.id.drag_layer);
         mFallbackRecentsView = findViewById(R.id.overview_panel);
+
+        RecentsActivityTracker.onRecentsActivityCreate(this);
     }
 
     @Override
@@ -57,6 +60,11 @@
     }
 
     @Override
+    public View getRootView() {
+        return mRecentsRootView;
+    }
+
+    @Override
     public <T extends View> T getOverviewPanel() {
         return (T) mFallbackRecentsView;
     }
@@ -73,4 +81,16 @@
 
     @Override
     public void invalidateParent(ItemInfo info) { }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        UiFactory.onStart(this);
+    }
+
+    @Override
+    public void onTrimMemory(int level) {
+        super.onTrimMemory(level);
+        UiFactory.onTrimMemory(this, level);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
new file mode 100644
index 0000000..6a82dc0
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+
+import java.lang.ref.WeakReference;
+import java.util.function.BiPredicate;
+
+/**
+ * Utility class to track create/destroy for RecentsActivity
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public class RecentsActivityTracker implements ActivityInitListener {
+
+    private static final Object LOCK = new Object();
+    private static WeakReference<RecentsActivityTracker> sTracker = new WeakReference<>(null);
+
+    private final BiPredicate<RecentsActivity, Boolean> mOnInitListener;
+
+    public RecentsActivityTracker(BiPredicate<RecentsActivity, Boolean> onInitListener) {
+        mOnInitListener = onInitListener;
+    }
+
+    @Override
+    public void register() {
+        synchronized (LOCK) {
+            sTracker = new WeakReference<>(this);
+        }
+    }
+
+    @Override
+    public void unregister() {
+        synchronized (LOCK) {
+            if (sTracker.get() == this) {
+                sTracker.clear();
+            }
+        }
+    }
+
+    public static void onRecentsActivityCreate(RecentsActivity activity) {
+        synchronized (LOCK) {
+            RecentsActivityTracker tracker = sTracker.get();
+            if (tracker != null && tracker.mOnInitListener.test(activity, false)) {
+                sTracker.clear();
+            }
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/SnapshotDragView.java b/quickstep/src/com/android/quickstep/SnapshotDragView.java
deleted file mode 100644
index 2ef3942..0000000
--- a/quickstep/src/com/android/quickstep/SnapshotDragView.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 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.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.view.MotionEvent;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.systemui.shared.recents.view.AnimateableViewBounds;
-
-/**
- * Floating view which shows the task snapshot allowing it to be dragged and placed.
- */
-public class SnapshotDragView extends AbstractFloatingView implements Insettable {
-
-    private final Launcher mLauncher;
-    private final Bitmap mSnapshot;
-    private final AnimateableViewBounds mViewBounds;
-
-    public SnapshotDragView(Launcher launcher, Bitmap snapshot) {
-        super(launcher, null);
-        mLauncher = launcher;
-        mSnapshot = snapshot;
-        mViewBounds = new AnimateableViewBounds(this, 0);
-        setWillNotDraw(false);
-        setClipToOutline(true);
-        setOutlineProvider(mViewBounds);
-    }
-
-    AnimateableViewBounds getViewBounds() {
-        return mViewBounds;
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        if (mSnapshot != null) {
-            setMeasuredDimension(mSnapshot.getWidth(), mSnapshot.getHeight());
-        } else {
-            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        }
-    }
-
-    @Override
-    public void setInsets(Rect insets) {
-
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        if (mSnapshot != null) {
-            canvas.drawBitmap(mSnapshot, 0, 0, null);
-        }
-    }
-
-    @Override
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        return false;
-    }
-
-    @Override
-    protected void handleClose(boolean animate) {
-        // We dont suupport animate.
-        mLauncher.getDragLayer().removeView(this);
-    }
-
-    @Override
-    public void logActionCommand(int command) {
-        // We should probably log the weather
-    }
-
-    @Override
-    protected boolean isOfType(int type) {
-        return (type & TYPE_QUICKSTEP_PREVIEW) != 0;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index ecd6c26..df7214e 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -47,7 +47,6 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
-import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.IOverviewProxy;
@@ -61,7 +60,6 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class TouchInteractionService extends Service {
 
-    public static final boolean DEBUG_SHOW_OVERVIEW_BUTTON = false;
     public static final boolean DEBUG_OPEN_OVERVIEW_VIA_ALT_TAB = false;
 
     private static final SparseArray<String> sMotionEventNames;
@@ -107,9 +105,7 @@
             mRecentsModel.setSystemUiProxy(mISystemUiProxy);
             RemoteRunnable.executeSafely(() -> mISystemUiProxy.setRecentsOnboardingText(
                     getResources().getString(R.string.recents_swipe_up_onboarding)));
-            Launcher launcher = (Launcher) LauncherAppState.getInstance(
-                    TouchInteractionService.this).getModel().getCallback();
-            UiFactory.onLauncherStateOrFocusChanged(launcher);
+            mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
         }
 
         @Override
@@ -174,6 +170,7 @@
     private MainThreadExecutor mMainThreadExecutor;
     private ISystemUiProxy mISystemUiProxy;
     private OverviewCommandHelper mOverviewCommandHelper;
+    private OverviewInteractionState mOverviewInteractionState;
 
     private Choreographer mMainThreadChoreographer;
     private Choreographer mBackgroundThreadChoreographer;
@@ -187,6 +184,7 @@
         mOverviewCommandHelper = new OverviewCommandHelper(this);
         mMainThreadChoreographer = Choreographer.getInstance();
         mEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
+        mOverviewInteractionState = OverviewInteractionState.getInstance(this);
 
         sConnected = true;
 
@@ -235,7 +233,8 @@
                 tracker = VelocityTracker.obtain();
             }
             return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel,
-                            mOverviewCommandHelper.homeIntent, mISystemUiProxy, mMainThreadExecutor,
+                            mOverviewCommandHelper.homeIntent,
+                            mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor,
                             mBackgroundThreadChoreographer, downHitTarget, tracker);
         }
     }
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index acf5db0..96cd4a0 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -15,22 +15,16 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
-import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
 import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_START_DURATION;
 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SWITCH;
 import static com.android.quickstep.TouchConsumer.isInteractionQuick;
-import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
+import static com.android.systemui.shared.recents.utilities.Utilities
+        .postAtFrontOfQueueAsynchronously;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 
 import android.animation.Animator;
-import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
@@ -53,14 +47,12 @@
 import android.view.ViewTreeObserver.OnDrawListener;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
@@ -69,7 +61,8 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+import com.android.quickstep.ActivityControlHelper.LayoutListener;
 import com.android.quickstep.TouchConsumer.InteractionType;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -119,7 +112,7 @@
 }
 
 @TargetApi(Build.VERSION_CODES.O)
-public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
+public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
     private static final boolean DEBUG_STATES = false;
 
@@ -193,6 +186,8 @@
     // 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;
     private int mTransitionDragLength;
 
@@ -206,12 +201,14 @@
 
     private final Context mContext;
     private final int mRunningTaskId;
+    private final ActivityControlHelper<T> mActivityControlHelper;
+    private final ActivityInitListener mActivityInitListener;
 
     private MultiStateCallback mStateCallback;
     private AnimatorPlaybackController mLauncherTransitionController;
 
-    private Launcher mLauncher;
-    private LauncherLayoutListener mLauncherLayoutListener;
+    private T mActivity;
+    private LayoutListener mLayoutListener;
     private RecentsView mRecentsView;
     private QuickScrubController mQuickScrubController;
 
@@ -234,10 +231,15 @@
     private long mLauncherFrameDrawnTime;
     private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
-    WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs) {
+    WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs,
+            ActivityControlHelper<T> controller) {
         mContext = context;
         mRunningTaskId = runningTaskInfo.id;
         mTouchTimeMs = touchTimeMs;
+        mActivityControlHelper = controller;
+        mActivityInitListener = mActivityControlHelper
+                .createActivityInitListener(this::onActivityInit);
+
         // Register the input consumer on the UI thread, to ensure that it runs after any pending
         // unregister calls
         mMainExecutor.execute(mInputConsumer::registerInputConsumer);
@@ -310,7 +312,8 @@
                 mSourceStackBounds.height() - mSourceInsets.bottom);
 
         Rect tempRect = new Rect();
-        RecentsView.getPageRect(dp, mContext, tempRect);
+        mTransitionDragLength = mActivityControlHelper
+                .getSwipeUpDestinationAndLength(dp, mContext, tempRect);
 
         mTargetRect.set(tempRect);
         mTargetRect.offset(mHomeStackBounds.left - mSourceStackBounds.left,
@@ -331,14 +334,6 @@
                 Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
                 Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
         mSourceRect.set(scaledTargetRect);
-
-        Rect targetInsets = dp.getInsets();
-        if (dp.isVerticalBarLayout()) {
-            int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
-            mTransitionDragLength = dp.hotseatBarSizePx + dp.hotseatBarSidePaddingPx + hotseatInset;
-        } else {
-            mTransitionDragLength = dp.heightPx - tempRect.bottom;
-        }
     }
 
     private long getFadeInDuration() {
@@ -353,40 +348,39 @@
         }
     }
 
-    @Override
-    protected boolean init(final Launcher launcher, boolean alreadyOnHome) {
-        if (launcher == mLauncher) {
+    public void initWhenReady() {
+        mActivityInitListener.register();
+    }
+
+    private boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
+        if (mActivity == activity) {
             return true;
         }
-        if (mLauncher != null) {
+        if (mActivity != null) {
             // The launcher may have been recreated as a result of device rotation.
             int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
             initStateCallbacks();
             mStateCallback.setState(oldState);
-            mLauncherLayoutListener.setHandler(null);
+            mLayoutListener.setHandler(null);
         }
         mWasLauncherAlreadyVisible = alreadyOnHome;
-        mLauncher = launcher;
+        mActivity = activity;
 
-        // For the duration of the gesture, lock the screen orientation to ensure that we do not
-        // rotate mid-quickscrub
-        mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
-
-        mRecentsView = mLauncher.getOverviewPanel();
+        mRecentsView = activity.getOverviewPanel();
         mQuickScrubController = mRecentsView.getQuickScrubController();
-        mLauncherLayoutListener = new LauncherLayoutListener(mLauncher);
+        mLayoutListener = mActivityControlHelper.createLayoutListener(mActivity);
 
         mStateCallback.setState(STATE_LAUNCHER_PRESENT);
         if (alreadyOnHome) {
-            onLauncherStart(launcher);
+            onLauncherStart(activity);
         } else {
-            launcher.setOnStartCallback(this::onLauncherStart);
+            activity.setOnStartCallback(this::onLauncherStart);
         }
         return true;
     }
 
-    private void onLauncherStart(final Launcher launcher) {
-        if (mLauncher != launcher) {
+    private void onLauncherStart(final T activity) {
+        if (mActivity != activity) {
             return;
         }
         if ((mStateCallback.getState() & STATE_HANDLER_INVALIDATED) != 0) {
@@ -394,31 +388,21 @@
         }
 
         mStateCallback.setState(STATE_LAUNCHER_STARTED);
-        LauncherState startState = mLauncher.getStateManager().getState();
-        if (startState.disableRestore) {
-            startState = mLauncher.getStateManager().getRestState();
-        }
-        mLauncher.getStateManager().setRestState(startState);
-
-        AbstractFloatingView.closeAllOpenViews(mLauncher, mWasLauncherAlreadyVisible);
+        mActivityControlHelper.prepareRecentsUI(mActivity, mWasLauncherAlreadyVisible);
+        AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible);
 
 
-        if (mWasLauncherAlreadyVisible && !mLauncher.getAppTransitionManager().isAnimating()) {
-            DeviceProfile dp = mLauncher.getDeviceProfile();
-            long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
-            mLauncherTransitionController = mLauncher.getStateManager()
-                    .createAnimationToNewWorkspace(OVERVIEW, accuracy);
+        if (mWasLauncherAlreadyVisible) {
+            mLauncherTransitionController = mActivityControlHelper
+                    .createControllerForVisibleActivity(activity);
             mLauncherTransitionController.dispatchOnStart();
             mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
 
             mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_LAUNCHER_DRAWN);
         } else {
             TraceHelper.beginSection("WTS-init");
-            mLauncher.getStateManager().goToState(OVERVIEW, false);
-            TraceHelper.partitionSection("WTS-init", "State changed");
-
             // TODO: Implement a better animation for fading in
-            View rootView = mLauncher.getRootView();
+            View rootView = activity.getRootView();
             rootView.setAlpha(0);
             rootView.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
 
@@ -427,21 +411,18 @@
                     TraceHelper.endSection("WTS-init", "Launcher frame is drawn");
                     rootView.post(() ->
                             rootView.getViewTreeObserver().removeOnDrawListener(this));
-                    if (launcher != mLauncher) {
+                    if (activity != mActivity) {
                         return;
                     }
 
                     mStateCallback.setState(STATE_LAUNCHER_DRAWN);
                 }
             });
-
-            // Optimization, hide the all apps view to prevent layout while initializing
-            mLauncher.getAppsView().setVisibility(View.GONE);
         }
 
         mRecentsView.showTask(mRunningTaskId);
         mRecentsView.setFirstTaskIconScaledDown(true /* isScaledDown */, false /* animate */);
-        mLauncherLayoutListener.open();
+        mLayoutListener.open();
     }
 
     public void setLauncherOnDrawCallback(Runnable callback) {
@@ -449,7 +430,7 @@
     }
 
     private void launcherFrameDrawn() {
-        View rootView = mLauncher.getRootView();
+        View rootView = mActivity.getRootView();
         if (rootView.getAlpha() < 1) {
             if (mGestureStarted) {
                 final MultiStateCallback callback = mStateCallback;
@@ -468,7 +449,7 @@
     }
 
     private void initializeLauncherAnimationController() {
-        mLauncherLayoutListener.setHandler(this);
+        mLayoutListener.setHandler(this);
         onLauncherLayoutChanged();
 
         final long transitionDelay = mLauncherFrameDrawnTime - mTouchTimeMs;
@@ -505,7 +486,7 @@
     }
 
     private void onQuickInteractionStart() {
-        mLauncher.getStateManager().goToState(FAST_OVERVIEW,
+        mActivityControlHelper.onQuickInteractionStart(mActivity,
                 mWasLauncherAlreadyVisible || mGestureStarted);
         mQuickScrubController.onQuickScrubStart(false);
     }
@@ -520,32 +501,14 @@
     }
 
     /**
-     * Called by {@link #mLauncherLayoutListener} when launcher layout changes
+     * Called by {@link #mLayoutListener} when launcher layout changes
      */
     public void onLauncherLayoutChanged() {
-        initTransitionEndpoints(mLauncher.getDeviceProfile());
+        initTransitionEndpoints(mActivity.getDeviceProfile());
 
         if (!mWasLauncherAlreadyVisible) {
-            float startProgress;
-            AllAppsTransitionController controller = mLauncher.getAllAppsController();
-
-            if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-                startProgress = 1;
-            } else {
-                float scrollRange = Math.max(controller.getShiftRange(), 1);
-                startProgress = (mTransitionDragLength / scrollRange) + 1;
-            }
-            AnimatorSet anim = new AnimatorSet();
-            ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(controller, ALL_APPS_PROGRESS,
-                    startProgress, OVERVIEW.getVerticalProgress(mLauncher));
-            shiftAnim.setInterpolator(LINEAR);
-            anim.play(shiftAnim);
-
-            // TODO: Link this animation to state animation, so that it is cancelled
-            // automatically on state change
-            anim.setDuration(mTransitionDragLength * 2);
-            mLauncherTransitionController =
-                    AnimatorPlaybackController.wrap(anim, mTransitionDragLength * 2);
+            mLauncherTransitionController = mActivityControlHelper
+                    .createControllerForHiddenActivity(mActivity, mTransitionDragLength);
             mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
         }
     }
@@ -658,14 +621,13 @@
                 }
             }
         }
-
         mRecentsAnimationWrapper.setController(controller, apps);
         setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
     }
 
     public void onRecentsAnimationCanceled() {
         mRecentsAnimationWrapper.setController(null, null);
-        clearReference();
+        mActivityInitListener.unregister();
         setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
     }
 
@@ -681,9 +643,10 @@
      * on both background and UI threads
      */
     private void notifyGestureStarted() {
-        final Launcher curLauncher = mLauncher;
-        if (curLauncher != null) {
-            curLauncher.onQuickstepGestureStarted(mWasLauncherAlreadyVisible);
+        final T curActivity = mActivity;
+        if (curActivity != null) {
+            mActivityControlHelper.onQuickstepGestureStarted(
+                    curActivity, mWasLauncherAlreadyVisible);
         }
     }
 
@@ -767,25 +730,20 @@
             mGestureEndCallback.run();
         }
 
-        clearReference();
+        mActivityInitListener.unregister();
         mInputConsumer.unregisterInputConsumer();
     }
 
     private void invalidateHandlerWithLauncher() {
         mLauncherTransitionController = null;
-        mLauncherLayoutListener.setHandler(null);
-        mLauncherLayoutListener.close(false);
-
-        // Restore the requested orientation to the user preference after the gesture has ended
-        mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_NONE);
+        mLayoutListener.finish();
 
         mRecentsView.setFirstTaskIconScaledDown(false /* isScaledDown */, false /* animate */);
     }
 
     private void resetStateForAnimationCancel() {
-        LauncherState startState = mLauncher.getStateManager().getRestState();
-        boolean animate = mWasLauncherAlreadyVisible || mGestureStarted;
-        mLauncher.getStateManager().goToState(startState, animate);
+        boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
+        mActivityControlHelper.onTransitionCancelled(mActivity, wasVisible);
     }
 
     public void layoutListenerClosed() {
@@ -814,17 +772,8 @@
                         if (taskView != null) {
                             // Defer finishing the animation until the next launcher frame with the
                             // new thumbnail
-                            ViewOnDrawExecutor executor = new ViewOnDrawExecutor() {
-                                @Override
-                                public void onViewDetachedFromWindow(View v) {
-                                    if (!isCompleted()) {
-                                        runAllTasks();
-                                    }
-                                }
-                            };
-                            executor.attachTo(mLauncher, taskView,
-                                    false /* waitForLoadAnimation */);
-                            executor.execute(finishTransitionRunnable);
+                            mActivityControlHelper.executeOnNextDraw(mActivity, taskView,
+                                    finishTransitionRunnable);
                             finishTransitionPosted = true;
                         }
                     }
@@ -840,8 +789,7 @@
     }
 
     private void setupLauncherUiAfterSwipeUpAnimation() {
-        // Re apply state in case we did something funky during the transition.
-        mLauncher.getStateManager().reapplyState();
+        mActivityControlHelper.onSwipeUpComplete(mActivity);
 
         // Animate the first icon.
         mRecentsView.setFirstTaskIconScaledDown(false /* isScaledDown */, true /* animate */);
@@ -895,4 +843,8 @@
         Log.d(TAG, "[" + System.identityHashCode(this) + "] Adding " + stateFlagStr + " to "
                 + currentStateStr);
     }
+
+    public void setGestureEndCallback(Runnable gestureEndCallback) {
+        mGestureEndCallback = gestureEndCallback;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/LauncherLayoutListener.java b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
similarity index 83%
rename from quickstep/src/com/android/quickstep/LauncherLayoutListener.java
rename to quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
index fbdbe7a..6b7143d 100644
--- a/quickstep/src/com/android/quickstep/LauncherLayoutListener.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
@@ -13,7 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.quickstep;
+package com.android.quickstep.views;
+
+import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
 
 import android.graphics.Rect;
 import android.view.MotionEvent;
@@ -21,11 +23,14 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
+import com.android.quickstep.ActivityControlHelper.LayoutListener;
+import com.android.quickstep.WindowTransformSwipeHandler;
 
 /**
  * Floating view which shows the task snapshot allowing it to be dragged and placed.
  */
-public class LauncherLayoutListener extends AbstractFloatingView implements Insettable {
+public class LauncherLayoutListener extends AbstractFloatingView
+        implements Insettable, LayoutListener {
 
     private final Launcher mLauncher;
     private WindowTransformSwipeHandler mHandler;
@@ -36,6 +41,7 @@
         setVisibility(INVISIBLE);
     }
 
+    @Override
     public void setHandler(WindowTransformSwipeHandler handler) {
         mHandler = handler;
     }
@@ -65,6 +71,7 @@
         }
     }
 
+    @Override
     public void open() {
         if (!mIsOpen) {
             mLauncher.getDragLayer().addView(this);
@@ -86,4 +93,11 @@
     protected boolean isOfType(int type) {
         return (type & TYPE_QUICKSTEP_PREVIEW) != 0;
     }
+
+    @Override
+    public void finish() {
+        setHandler(null);
+        close(false);
+        mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_NONE);
+    }
 }
diff --git a/res/layout/work_tab_bottom_user_education_view.xml b/res/layout/work_tab_bottom_user_education_view.xml
index dc6854e..ba6a939 100644
--- a/res/layout/work_tab_bottom_user_education_view.xml
+++ b/res/layout/work_tab_bottom_user_education_view.xml
@@ -20,6 +20,7 @@
     android:layout_gravity="bottom"
     android:background="?android:attr/colorAccent"
     android:elevation="2dp"
+    android:focusable="true"
     android:orientation="horizontal">
 
   <ImageView
@@ -42,6 +43,7 @@
         android:layout_marginTop="12dp"
         android:layout_marginEnd="12dp"
         android:layout_gravity="right"
+        android:contentDescription="@string/bottom_work_tab_user_education_close_button"
         android:src="@drawable/ic_close"/>
 
     <TextView
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d971a36..0b45b8e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -330,5 +330,6 @@
     <string name="work_mode_on_label">Managed by your organization</string>
     <!-- This string appears under a the label of a toggle in the work profile tab on a user's phone. It describes the status of the toggle, "Work profile," when it's turned off. "Work profile" means a separate profile on a user's phone that's speficially for their work apps and is managed by their company.-->
     <string name="work_mode_off_label">Notifications and apps are off</string>
-
+    <string name="bottom_work_tab_user_education_close_button">Close</string>
+    <string name="bottom_work_tab_user_education_closed">Closed</string>
 </resources>
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 35edaf4..458f7b2 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -55,6 +55,8 @@
     private ActionMode mCurrentActionMode;
     protected boolean mIsSafeModeEnabled;
 
+    private OnStartCallback mOnStartCallback;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -85,6 +87,8 @@
 
     public abstract <T extends View> T getOverviewPanel();
 
+    public abstract View getRootView();
+
     public abstract BadgeInfo getBadgeInfoForItem(ItemInfo info);
 
     public abstract void invalidateParent(ItemInfo info);
@@ -188,4 +192,26 @@
     protected boolean onErrorStartingShortcut(Intent intent, ItemInfo info) {
         return false;
     }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+
+        if (mOnStartCallback != null) {
+            mOnStartCallback.onActivityStart(this);
+            mOnStartCallback = null;
+        }
+    }
+
+    public <T extends BaseDraggingActivity> void setOnStartCallback(OnStartCallback<T> callback) {
+        mOnStartCallback = callback;
+    }
+
+    /**
+     * Callback for listening for onStart
+     */
+    public interface OnStartCallback<T extends BaseDraggingActivity> {
+
+        void onActivityStart(T activity);
+    }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index a38ce07..ee6dd59 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -209,7 +209,6 @@
 
     @Thunk boolean mWorkspaceLoading = true;
 
-    private OnStartCallback mOnStartCallback;
     private OnResumeCallback mOnResumeCallback;
 
     private ViewOnDrawExecutor mPendingExecutor;
@@ -767,10 +766,6 @@
         super.onStart();
         FirstFrameAnimatorHelper.setIsVisible(true);
 
-        if (mOnStartCallback != null) {
-            mOnStartCallback.onLauncherStart(this);
-            mOnStartCallback = null;
-        }
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onStart();
         }
@@ -1177,6 +1172,7 @@
         return mAllAppsController;
     }
 
+    @Override
     public LauncherRootView getRootView() {
         return (LauncherRootView) mLauncherView;
     }
@@ -1775,10 +1771,6 @@
         mOnResumeCallback = callback;
     }
 
-    public void setOnStartCallback(OnStartCallback callback) {
-        mOnStartCallback = callback;
-    }
-
     /**
      * Implementation of the method from LauncherModel.Callbacks.
      */
@@ -2450,12 +2442,4 @@
 
         void onLauncherResume();
     }
-
-    /**
-     * Callback for listening for onStart
-     */
-    public interface OnStartCallback {
-
-        void onLauncherStart(Launcher launcher);
-    }
 }
diff --git a/src/com/android/launcher3/views/BottomUserEducationView.java b/src/com/android/launcher3/views/BottomUserEducationView.java
index ba78cf6..a291fc6 100644
--- a/src/com/android/launcher3/views/BottomUserEducationView.java
+++ b/src/com/android/launcher3/views/BottomUserEducationView.java
@@ -22,11 +22,15 @@
 import android.view.LayoutInflater;
 import android.view.TouchDelegate;
 import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
 
+import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
+
 public class BottomUserEducationView extends AbstractSlideInView implements Insettable {
 
     private static final String KEY_SHOWED_BOTTOM_USER_EDUCATION = "showed_bottom_user_education";
@@ -90,6 +94,10 @@
             // close action.
             mLauncher.getSharedPrefs().edit()
                     .putBoolean(KEY_SHOWED_BOTTOM_USER_EDUCATION, true).apply();
+            sendCustomAccessibilityEvent(
+                    BottomUserEducationView.this,
+                    AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+                    getContext().getString(R.string.bottom_work_tab_user_education_closed));
         }
     }