Merge "Using new APIs for disabling metaData table" into ub-launcher3-master
diff --git a/proguard.flags b/proguard.flags
index b8cade5..086337d 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -39,7 +39,7 @@
   public int getY();
 }
 
--keep class com.android.launcher3.dragndrop.DragLayer$LayoutParams {
+-keep class com.android.launcher3.views.BaseDragLayer$LayoutParams {
   public void setWidth(int);
   public int getWidth();
   public void setHeight(int);
@@ -102,6 +102,11 @@
     public <init>(...);
 }
 
+# InstantAppResolver
+-keep class com.android.quickstep.InstantAppResolverImpl {
+    public <init>(...);
+}
+
 -keep interface com.android.launcher3.userevent.nano.LauncherLogProto.** {
   *;
 }
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index d531a46..8b28597 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -47,7 +47,21 @@
         <!-- 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
+            android:name="com.android.quickstep.LauncherSearchIndexablesProvider"
+            android:authorities="com.android.launcher3"
+            android:grantUriPermissions="true"
+            android:multiprocess="true"
+            android:permission="android.permission.READ_SEARCH_INDEXABLES"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" />
+            </intent-filter>
+        </provider>
     </application>
 
 </manifest>
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 6e62add..6e56055 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/layout/fallback_recents_activity.xml b/quickstep/res/layout/fallback_recents_activity.xml
new file mode 100644
index 0000000..b3fc4ed
--- /dev/null
+++ b/quickstep/res/layout/fallback_recents_activity.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+     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.
+-->
+<com.android.quickstep.RecentsRootView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/drag_layer"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.android.quickstep.FallbackRecentsView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/overview_panel"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:theme="@style/HomeScreenElementTheme" />
+
+</com.android.quickstep.RecentsRootView>
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index 9f4f8a1..54a90cf 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -14,7 +14,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.quickstep.RecentsView
+<com.android.quickstep.views.LauncherRecentsView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:theme="@style/HomeScreenElementTheme"
     android:layout_width="match_parent"
@@ -24,8 +24,4 @@
     android:alpha="0.0"
     android:visibility="invisible" >
 
-    <com.android.launcher3.uioverrides.WorkspaceCard
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-
-</com.android.quickstep.RecentsView>
\ No newline at end of file
+</com.android.quickstep.views.LauncherRecentsView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 91b6aa3..0ac2b11 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -13,12 +13,12 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.quickstep.TaskView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.quickstep.views.TaskView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:elevation="4dp">
 
-    <com.android.quickstep.TaskThumbnailView
+    <com.android.quickstep.views.TaskThumbnailView
         android:id="@+id/snapshot"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -29,4 +29,4 @@
         android:layout_width="@dimen/task_thumbnail_icon_size"
         android:layout_height="@dimen/task_thumbnail_icon_size"
         android:layout_gravity="top|center_horizontal" />
-</com.android.quickstep.TaskView>
\ No newline at end of file
+</com.android.quickstep.views.TaskView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task_menu.xml b/quickstep/res/layout/task_menu.xml
index 6e3fb4f..b846665 100644
--- a/quickstep/res/layout/task_menu.xml
+++ b/quickstep/res/layout/task_menu.xml
@@ -14,7 +14,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.quickstep.TaskMenuView
+<com.android.quickstep.views.TaskMenuView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="@dimen/bg_popup_item_width"
     android:layout_height="wrap_content"
@@ -33,4 +33,4 @@
             android:paddingTop="18dp"
             android:drawablePadding="8dp"
             android:gravity="center_horizontal"/>
-</com.android.quickstep.TaskMenuView>
\ No newline at end of file
+</com.android.quickstep.views.TaskMenuView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index cf86176..e61e359 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -16,8 +16,6 @@
 
 <resources>
 
-    <dimen name="options_menu_icon_size">24dp</dimen>
-
     <dimen name="task_thumbnail_top_margin">24dp</dimen>
     <dimen name="task_thumbnail_icon_size">48dp</dimen>
     <dimen name="task_menu_background_radius">12dp</dimen>
@@ -33,12 +31,10 @@
     <dimen name="quickstep_fling_threshold_velocity">500dp</dimen>
     <dimen name="quickstep_fling_min_velocity">250dp</dimen>
 
-    <dimen name="workspace_overview_offset_x">-24dp</dimen>
-
     <!-- Launcher app transition -->
     <dimen name="content_trans_y">25dp</dimen>
     <dimen name="workspace_trans_y">80dp</dimen>
-    <dimen name="recents_adjacent_trans_x">120dp</dimen>
+    <dimen name="recents_adjacent_trans_x">140dp</dimen>
     <dimen name="recents_adjacent_trans_y">80dp</dimen>
     <fraction name="recents_adjacent_scale">150%</fraction>
 </resources>
diff --git a/quickstep/res/values/override.xml b/quickstep/res/values/override.xml
index ba99d81..2bd9f8f 100644
--- a/quickstep/res/values/override.xml
+++ b/quickstep/res/values/override.xml
@@ -16,5 +16,7 @@
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
   <string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
+
+  <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
 </resources>
 
diff --git a/quickstep/res/xml/indexable_launcher_prefs.xml b/quickstep/res/xml/indexable_launcher_prefs.xml
new file mode 100644
index 0000000..2655402
--- /dev/null
+++ b/quickstep/res/xml/indexable_launcher_prefs.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 Google Inc.
+
+     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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <SwitchPreference
+        android:key="pref_add_icon_to_home"
+        android:title="@string/auto_add_shortcuts_label"
+        android:summary="@string/auto_add_shortcuts_description"
+        android:defaultValue="true"
+        />
+
+    <ListPreference
+        android:key="pref_override_icon_shape"
+        android:title="@string/icon_shape_override_label"
+        android:summary="@string/icon_shape_override_label_location"
+        android:entries="@array/icon_shape_override_paths_names"
+        android:entryValues="@array/icon_shape_override_paths_values"
+        android:defaultValue=""
+        android:persistent="false" />
+
+</PreferenceScreen>
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 872e6ca..f919339 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -15,27 +15,92 @@
  */
 package com.android.launcher3;
 
+import static com.android.systemui.shared.recents.utilities.Utilities
+        .postAtFrontOfQueueAsynchronously;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.Handler;
+import android.support.annotation.BinderThread;
+import android.support.annotation.UiThread;
 
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
-import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
+@TargetApi(Build.VERSION_CODES.P)
+public abstract class LauncherAnimationRunner extends AnimatorListenerAdapter
+        implements RemoteAnimationRunnerCompat {
 
-public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat {
+    private static final int REFRESH_RATE_MS = 16;
 
-    AnimatorSet mAnimator;
-    private Launcher mLauncher;
+    private final Handler mHandler;
 
-    LauncherAnimationRunner(Launcher launcher) {
-        mLauncher = launcher;
+    private Runnable mSysFinishRunnable;
+
+    private AnimatorSet mAnimator;
+
+    public LauncherAnimationRunner(Handler handler) {
+        mHandler = handler;
     }
 
+    @BinderThread
+    @Override
+    public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats, Runnable runnable) {
+        postAtFrontOfQueueAsynchronously(mHandler, () -> {
+            // Finish any previous animation
+            finishSystemAnimation();
+
+            mSysFinishRunnable = runnable;
+            mAnimator = getAnimator(targetCompats);
+            if (mAnimator == null) {
+                finishSystemAnimation();
+                return;
+            }
+            mAnimator.addListener(this);
+            mAnimator.start();
+            // Because t=0 has the app icon in its original spot, we can skip the
+            // first frame and have the same movement one frame earlier.
+            mAnimator.setCurrentPlayTime(REFRESH_RATE_MS);
+
+        });
+    }
+
+
+    @UiThread
+    public abstract AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats);
+
+    @UiThread
+    @Override
+    public void onAnimationEnd(Animator animation) {
+        if (animation == mAnimator) {
+            mAnimator = null;
+            finishSystemAnimation();
+        }
+    }
+
+    /**
+     * Called by the system
+     */
+    @BinderThread
     @Override
     public void onAnimationCancelled() {
-        postAtFrontOfQueueAsynchronously(mLauncher.getWindow().getDecorView().getHandler(), () -> {
+        postAtFrontOfQueueAsynchronously(mHandler, () -> {
             if (mAnimator != null) {
-                mAnimator.cancel();
+                mAnimator.removeListener(this);
+                mAnimator.end();
+                mAnimator = null;
             }
         });
     }
+
+    @UiThread
+    private void finishSystemAnimation() {
+        if (mSysFinishRunnable != null) {
+            mSysFinishRunnable.run();
+            mSysFinishRunnable = null;
+        }
+    }
 }
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index b97669b..0dbf404 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
 import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
-import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
@@ -32,6 +31,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -40,6 +40,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Handler;
+import android.os.Looper;
 import android.util.Log;
 import android.view.Surface;
 import android.view.View;
@@ -58,8 +59,9 @@
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.quickstep.RecentsAnimationInterpolator;
 import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
-import com.android.quickstep.RecentsView;
-import com.android.quickstep.TaskView;
+import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -78,11 +80,14 @@
         implements OnDeviceProfileChangeListener {
 
     private static final String TAG = "LauncherTransition";
-    private static final int REFRESH_RATE_MS = 16;
+    private static final int STATUS_BAR_TRANSITION_DURATION = 120;
 
     private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
             "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
 
+    private static final int APP_LAUNCH_DURATION = 500;
+    // Use a shorter duration for x or y translation to create a curve effect
+    private static final int APP_LAUNCH_CURVED_DURATION = 233;
     private static final int RECENTS_LAUNCH_DURATION = 336;
     private static final int LAUNCHER_RESUME_START_DELAY = 100;
     private static final int CLOSING_TRANSITION_DURATION_MS = 350;
@@ -93,7 +98,9 @@
 
     private final DragLayer mDragLayer;
     private final Launcher mLauncher;
-    private DeviceProfile mDeviceProfile;
+
+    private final Handler mHandler;
+    private final boolean mIsRtl;
 
     private final float mContentTransY;
     private final float mWorkspaceTransY;
@@ -101,17 +108,35 @@
     private final float mRecentsTransY;
     private final float mRecentsScale;
 
+    private DeviceProfile mDeviceProfile;
     private View mFloatingView;
-    private boolean mIsRtl;
 
-    private LauncherTransitionAnimator mCurrentAnimator;
+    private final AnimatorListenerAdapter mUiResetListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mDragLayer.setAlpha(1f);
+            mDragLayer.setTranslationY(0f);
+
+            View appsView = mLauncher.getAppsView();
+            appsView.setAlpha(1f);
+            appsView.setTranslationY(0f);
+        }
+    };
+
+    private final AnimatorListenerAdapter mReapplyStateListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mLauncher.getStateManager().reapplyState();
+        }
+    };
 
     public LauncherAppTransitionManagerImpl(Context context) {
         mLauncher = Launcher.getLauncher(context);
         mDragLayer = mLauncher.getDragLayer();
+        mHandler = new Handler(Looper.getMainLooper());
+        mIsRtl = Utilities.isRtl(mLauncher.getResources());
         mDeviceProfile = mLauncher.getDeviceProfile();
 
-        mIsRtl = Utilities.isRtl(mLauncher.getResources());
 
         Resources res = mLauncher.getResources();
         mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
@@ -129,26 +154,6 @@
         mDeviceProfile = dp;
     }
 
-    private void setCurrentAnimator(LauncherTransitionAnimator animator) {
-        if (isAnimating()) {
-            mCurrentAnimator.cancel();
-        }
-        mCurrentAnimator = animator;
-    }
-
-    @Override
-    public void finishLauncherAnimation() {
-        if (isAnimating()) {
-            mCurrentAnimator.finishLauncherAnimation();
-        }
-        mCurrentAnimator = null;
-    }
-
-    @Override
-    public boolean isAnimating() {
-        return mCurrentAnimator != null && mCurrentAnimator.isRunning();
-    }
-
     /**
      * @return ActivityOptions with remote animations that controls how the window of the opening
      *         targets are displayed.
@@ -157,57 +162,37 @@
     public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
         if (hasControlRemoteAppTransitionPermission()) {
             try {
-                RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mLauncher) {
-                    @Override
-                    public void onAnimationStart(RemoteAnimationTargetCompat[] targets,
-                                                 Runnable finishedCallback) {
-                        // Post at front of queue ignoring sync barriers to make sure it gets
-                        // processed before the next frame.
-                        postAtFrontOfQueueAsynchronously(v.getHandler(), () -> {
-                            final boolean removeTrackingView;
-                            LauncherTransitionAnimator animator =
-                                    composeRecentsLaunchAnimator(v, targets);
-                            if (animator != null) {
-                                // We are animating the task view directly, do not remove it after
-                                removeTrackingView = false;
-                            } else {
-                                animator = composeAppLaunchAnimator(v, targets);
-                                // A new floating view is created for the animation, remove it after
-                                removeTrackingView = true;
-                            }
+                RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler) {
 
-                            setCurrentAnimator(animator);
-                            mAnimator = animator.getAnimatorSet();
-                            mAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
+                        Animator[] anims = composeRecentsLaunchAnimator(v, targetCompats);
+                        AnimatorSet anim = new AnimatorSet();
+                        if (anims != null) {
+                            anim.playTogether(anims);
+                        } else {
+                            anim.play(getLauncherAnimators(v, targetCompats));
+                            anim.play(getWindowAnimators(v, targetCompats));
+                            anim.addListener(new AnimatorListenerAdapter() {
                                 @Override
                                 public void onAnimationEnd(Animator animation) {
                                     // Reset launcher to normal state
                                     v.setVisibility(View.VISIBLE);
-                                    if (removeTrackingView) {
-                                        ((ViewGroup) mDragLayer.getParent()).removeView(
-                                                mFloatingView);
-                                    }
-
-                                    mDragLayer.setAlpha(1f);
-                                    mDragLayer.setTranslationY(0f);
-
-                                    View appsView = mLauncher.getAppsView();
-                                    appsView.setAlpha(1f);
-                                    appsView.setTranslationY(0f);
-
-                                    finishedCallback.run();
+                                    ((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
                                 }
                             });
-                            mAnimator.start();
-                            // Because t=0 has the app icon in its original spot, we can skip the
-                            // first frame and have the same movement one frame earlier.
-                            mAnimator.setCurrentPlayTime(REFRESH_RATE_MS);
-                        });
+                            anim.addListener(mUiResetListener);
+                        }
+                        mLauncher.getStateManager().setCurrentAnimation(anim);
+                        return anim;
                     }
                 };
 
-                return ActivityOptionsCompat.makeRemoteAnimation(
-                        new RemoteAnimationAdapterCompat(runner, 500, 380));
+                int duration = findTaskViewToLaunch(launcher, v, null) != null
+                        ? RECENTS_LAUNCH_DURATION : APP_LAUNCH_DURATION;
+                int statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION;
+                return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
+                        runner, duration, statusBarTransitionDelay));
             } catch (NoClassDefFoundError e) {
                 // Gracefully fall back to default launch options if the user's platform doesn't
                 // have the latest changes.
@@ -217,15 +202,40 @@
     }
 
     /**
-     * Composes the animations for a launch from the recents list if possible.
+     * Try to find a TaskView that corresponds with the component of the launched view.
+     *
+     * If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation.
+     * Otherwise, we will assume we are using a normal app transition, but it's possible that the
+     * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
      */
-    private LauncherTransitionAnimator composeRecentsLaunchAnimator(View v,
-            RemoteAnimationTargetCompat[] targets) {
-        // Ensure recents is actually visible
-        if (!mLauncher.getStateManager().getState().overviewUi) {
-            return null;
+    private TaskView findTaskViewToLaunch(
+            BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets) {
+        if (v instanceof TaskView) {
+            return (TaskView) v;
+        }
+        RecentsView recentsView = activity.getOverviewPanel();
+
+        // It's possible that the launched view can still be resolved to a visible task view, check
+        // the task id of the opening task and see if we can find a match.
+        if (v.getTag() instanceof ItemInfo) {
+            ItemInfo itemInfo = (ItemInfo) v.getTag();
+            ComponentName componentName = itemInfo.getTargetComponent();
+            if (componentName != null) {
+                for (int i = 0; i < recentsView.getChildCount(); i++) {
+                    TaskView taskView = (TaskView) recentsView.getPageAt(i);
+                    if (recentsView.isTaskViewVisible(taskView)) {
+                        Task task = taskView.getTask();
+                        if (componentName.equals(task.key.getComponent())) {
+                            return taskView;
+                        }
+                    }
+                }
+            }
         }
 
+        if (targets == null) {
+            return null;
+        }
         // Resolve the opening task id
         int openingTaskId = -1;
         for (RemoteAnimationTargetCompat target : targets) {
@@ -242,25 +252,39 @@
 
         // If the opening task id is not currently visible in overview, then fall back to normal app
         // icon launch animation
-        RecentsView recentsView = mLauncher.getOverviewPanel();
         TaskView taskView = recentsView.getTaskView(openingTaskId);
         if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
             return null;
         }
+        return taskView;
+    }
+
+    /**
+     * Composes the animations for a launch from the recents list if possible.
+     */
+    private Animator[] composeRecentsLaunchAnimator(View v,
+            RemoteAnimationTargetCompat[] targets) {
+        // Ensure recents is actually visible
+        if (!mLauncher.getStateManager().getState().overviewUi) {
+            return null;
+        }
+
+        RecentsView recentsView = mLauncher.getOverviewPanel();
+        boolean launcherClosing = launcherIsATargetWithMode(targets, MODE_CLOSING);
+        boolean skipLauncherChanges = !launcherClosing;
+
+        TaskView taskView = findTaskViewToLaunch(mLauncher, v, targets);
+        if (taskView == null) {
+            return null;
+        }
 
         // Found a visible recents task that matches the opening app, lets launch the app from there
         Animator launcherAnim;
-        AnimatorListenerAdapter windowAnimEndListener;
-        boolean launcherClosing = launcherIsATargetWithMode(targets, MODE_CLOSING);
+        final AnimatorListenerAdapter windowAnimEndListener;
         if (launcherClosing) {
             launcherAnim = getRecentsLauncherAnimator(recentsView, taskView);
-            windowAnimEndListener = new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    // Make sure recents gets fixed up by resetting task alphas and scales, etc.
-                    mLauncher.getStateManager().reapplyState();
-                }
-            };
+            // Make sure recents gets fixed up by resetting task alphas and scales, etc.
+            windowAnimEndListener = mReapplyStateListener;
         } else {
             AnimatorPlaybackController controller =
                     mLauncher.getStateManager()
@@ -275,10 +299,9 @@
             };
         }
 
-        MutableBoolean skipLauncherChanges = new MutableBoolean(!launcherClosing);
         Animator windowAnim = getRecentsWindowAnimator(taskView, skipLauncherChanges, targets);
         windowAnim.addListener(windowAnimEndListener);
-        return new LauncherTransitionAnimator(launcherAnim, windowAnim, skipLauncherChanges);
+        return new Animator[] {launcherAnim, windowAnim};
     }
 
     /**
@@ -295,7 +318,7 @@
         boolean launchingCenterTask = launchedTaskIndex == centerTaskIndex;
         boolean isRtl = recentsView.isRtl();
         if (launchingCenterTask) {
-            if (launchedTaskIndex - 1 >= recentsView.getFirstTaskIndex()) {
+            if (launchedTaskIndex - 1 >= 0) {
                 TaskView adjacentPage1 = (TaskView) recentsView.getPageAt(launchedTaskIndex - 1);
                 ObjectAnimator adjacentTask1ScaleAndTranslate =
                         LauncherAnimUtils.ofPropertyValuesHolder(adjacentPage1,
@@ -317,29 +340,27 @@
                                         .build());
                 launcherAnimator.play(adjacentTask2ScaleAndTranslate);
             }
-        } else if (centerTaskIndex >= recentsView.getFirstTaskIndex()) {
+        } else {
             // We are launching an adjacent task, so parallax the center and other adjacent task.
             TaskView centerTask = (TaskView) recentsView.getPageAt(centerTaskIndex);
-            float translationX = Math.abs(v.getTranslationX());
-            ObjectAnimator centerTaskParallaxToRight =
+            float translationX = mRecentsTransX / 2;
+            ObjectAnimator centerTaskParallaxOffscreen =
                     LauncherAnimUtils.ofPropertyValuesHolder(centerTask,
                             new PropertyListBuilder()
-                                    .scale(v.getScaleX())
                                     .translationX(isRtl ? -translationX : translationX)
                                     .build());
-            launcherAnimator.play(centerTaskParallaxToRight);
+            launcherAnimator.play(centerTaskParallaxOffscreen);
             int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - launchedTaskIndex);
-            if (otherAdjacentTaskIndex >= recentsView.getFirstTaskIndex()
+            if (otherAdjacentTaskIndex >= 0
                     && otherAdjacentTaskIndex < recentsView.getPageCount()) {
                 TaskView otherAdjacentTask = (TaskView) recentsView.getPageAt(
                         otherAdjacentTaskIndex);
-                ObjectAnimator otherAdjacentTaskParallaxToRight =
+                ObjectAnimator otherAdjacentTaskParallaxOffscreen =
                         LauncherAnimUtils.ofPropertyValuesHolder(otherAdjacentTask,
                                 new PropertyListBuilder()
-                                        .translationX(otherAdjacentTask.getTranslationX()
-                                                + (isRtl ? -translationX : translationX))
+                                        .translationX(isRtl ? -translationX : translationX)
                                         .build());
-                launcherAnimator.play(otherAdjacentTaskParallaxToRight);
+                launcherAnimator.play(otherAdjacentTaskParallaxOffscreen);
             }
         }
 
@@ -368,7 +389,7 @@
      * @return Animator that controls the window of the opening targets for the recents launch
      * animation.
      */
-    private ValueAnimator getRecentsWindowAnimator(TaskView v, MutableBoolean skipLauncherChanges,
+    private ValueAnimator getRecentsWindowAnimator(TaskView v, boolean skipLauncherChanges,
             RemoteAnimationTargetCompat[] targets) {
         Rect taskViewBounds = new Rect();
         mDragLayer.getDescendantRectRelativeToSelf(v, taskViewBounds);
@@ -404,7 +425,7 @@
                 final float percent = animation.getAnimatedFraction();
                 TaskWindowBounds tw = recentsInterpolator.interpolate(percent);
 
-                if (!skipLauncherChanges.value) {
+                if (!skipLauncherChanges) {
                     v.setScaleX(tw.taskScale);
                     v.setScaleY(tw.taskScale);
                     v.setTranslationX(tw.taskX);
@@ -419,9 +440,8 @@
                 crop.set(tw.winCrop);
 
                 // Fade in the app window.
-                float alphaDelay = 0;
                 float alphaDuration = 75;
-                float alpha = getValue(0f, 1f, alphaDelay, alphaDuration,
+                float alpha = getValue(0f, 1f, 0, alphaDuration,
                         appAnimator.getDuration() * percent, Interpolators.LINEAR);
 
                 TransactionCompat t = new TransactionCompat();
@@ -435,7 +455,7 @@
                         t.setMatrix(target.leash, matrix);
                         t.setWindowCrop(target.leash, crop);
 
-                        if (!skipLauncherChanges.value) {
+                        if (!skipLauncherChanges) {
                             t.deferTransactionUntil(target.leash, surface, frameNumber);
                         }
                     }
@@ -453,15 +473,6 @@
     }
 
     /**
-     * Composes the animations for a launch from an app icon.
-     */
-    private LauncherTransitionAnimator composeAppLaunchAnimator(View v,
-            RemoteAnimationTargetCompat[] targets) {
-        return new LauncherTransitionAnimator(getLauncherAnimators(v, targets),
-                getWindowAnimators(v, targets));
-    }
-
-    /**
      * @return Animators that control the movements of the Launcher and icon of the opening target.
      */
     private AnimatorSet getLauncherAnimators(View v, RemoteAnimationTargetCompat[] targets) {
@@ -545,10 +556,8 @@
         } else {
             mDragLayer.getDescendantRectRelativeToSelf(v, rect);
         }
-        final int viewLocationStart = mIsRtl
-                ? mDeviceProfile.widthPx - rect.right
-                : rect.left;
-        final int viewLocationTop = rect.top;
+        int viewLocationLeft = rect.left;
+        int viewLocationTop = rect.top;
 
         float startScale = 1f;
         if (isBubbleTextView && !isDeepShortcutTextView) {
@@ -561,12 +570,24 @@
         } else {
             rect.set(0, 0, rect.width(), rect.height());
         }
+        viewLocationLeft += rect.left;
+        viewLocationTop += rect.top;
+        int viewLocationStart = mIsRtl
+                ? mDeviceProfile.widthPx - rect.right
+                : viewLocationLeft;
         LayoutParams lp = new LayoutParams(rect.width(), rect.height());
         lp.ignoreInsets = true;
-        lp.setMarginStart(viewLocationStart + rect.left);
-        lp.topMargin = viewLocationTop + rect.top;
+        lp.setMarginStart(viewLocationStart);
+        lp.topMargin = viewLocationTop;
         mFloatingView.setLayoutParams(lp);
 
+        // Set the properties here already to make sure they'are available when running the first
+        // animation frame.
+        mFloatingView.setLeft(viewLocationLeft);
+        mFloatingView.setTop(viewLocationTop);
+        mFloatingView.setRight(viewLocationLeft + rect.width());
+        mFloatingView.setBottom(viewLocationTop + rect.height());
+
         // Swap the two views in place.
         ((ViewGroup) mDragLayer.getParent()).addView(mFloatingView);
         v.setVisibility(View.INVISIBLE);
@@ -587,8 +608,8 @@
 
         // Adjust the duration to change the "curve" of the app icon to the center.
         boolean isBelowCenterY = lp.topMargin < centerY;
-        x.setDuration(isBelowCenterY ? 500 : 233);
-        y.setDuration(isBelowCenterY ? 233 : 500);
+        x.setDuration(isBelowCenterY ? APP_LAUNCH_DURATION : APP_LAUNCH_CURVED_DURATION);
+        y.setDuration(isBelowCenterY ? APP_LAUNCH_CURVED_DURATION : APP_LAUNCH_DURATION);
         x.setInterpolator(Interpolators.AGGRESSIVE_EASE);
         y.setInterpolator(Interpolators.AGGRESSIVE_EASE);
         appIconAnimatorSet.play(x);
@@ -601,7 +622,7 @@
         float scale = Math.max(maxScaleX, maxScaleY);
         ObjectAnimator scaleAnim = ObjectAnimator
                 .ofFloat(mFloatingView, SCALE_PROPERTY, startScale, scale);
-        scaleAnim.setDuration(500).setInterpolator(Interpolators.EXAGGERATED_EASE);
+        scaleAnim.setDuration(APP_LAUNCH_DURATION).setInterpolator(Interpolators.EXAGGERATED_EASE);
         appIconAnimatorSet.play(scaleAnim);
 
         // Fade out the app icon.
@@ -619,7 +640,13 @@
      */
     private ValueAnimator getWindowAnimators(View v, RemoteAnimationTargetCompat[] targets) {
         Rect bounds = new Rect();
-        if (v instanceof BubbleTextView) {
+        boolean isDeepShortcutTextView = v instanceof DeepShortcutTextView
+                && v.getParent() != null && v.getParent() instanceof DeepShortcutView;
+        if (isDeepShortcutTextView) {
+            // Deep shortcut views have their icon drawn in a sibling view.
+            DeepShortcutView view = (DeepShortcutView) v.getParent();
+            mDragLayer.getDescendantRectRelativeToSelf(view.getIconView(), bounds);
+        } else if (v instanceof BubbleTextView) {
             ((BubbleTextView) v).getIconBounds(bounds);
         } else {
             mDragLayer.getDescendantRectRelativeToSelf(v, bounds);
@@ -630,7 +657,7 @@
         Matrix matrix = new Matrix();
 
         ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
-        appAnimator.setDuration(500);
+        appAnimator.setDuration(APP_LAUNCH_DURATION);
         appAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             boolean isFirstFrame = true;
 
@@ -670,9 +697,8 @@
                 matrix.postTranslate(transX0, transY0);
 
                 // Fade in the app window.
-                float alphaDelay = 0;
                 float alphaDuration = 60;
-                float alpha = getValue(0f, 1f, alphaDelay, alphaDuration,
+                float alpha = getValue(0f, 1f, 0, alphaDuration,
                         appAnimator.getDuration() * percent, Interpolators.LINEAR);
 
                 // Animate the window crop so that it starts off as a square, and then reveals
@@ -717,6 +743,7 @@
             try {
                 RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
                 definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
+                        WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
                         new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(),
                                 CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
 
@@ -744,40 +771,25 @@
      *         ie. pressing home, swiping up from nav bar.
      */
     private RemoteAnimationRunnerCompat getWallpaperOpenRunner() {
-        return new LauncherAnimationRunner(mLauncher) {
+        return new LauncherAnimationRunner(mHandler) {
             @Override
-            public void onAnimationStart(RemoteAnimationTargetCompat[] targets,
-                                         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)
-                            || !launcherIsATargetWithMode(targets, MODE_OPENING)) {
-                        // We use a separate transition for Overview mode. And we can skip the
-                        // animation in cases where Launcher is not in the set of opening targets.
-                        // This can happen when Launcher is already visible. ie. Closing a dialog.
-                        setCurrentAnimator(null);
-                        finishedCallback.run();
-                        return;
-                    }
+            public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
+                if (mLauncher.getStateManager().getState().overviewUi) {
+                    // We use a separate transition for Overview mode.
+                    return null;
+                }
 
-                    LauncherTransitionAnimator animator = new LauncherTransitionAnimator(
-                            getLauncherResumeAnimation(), getClosingWindowAnimators(targets));
-                    setCurrentAnimator(animator);
-                    mAnimator = animator.getAnimatorSet();
-                    mAnimator.addListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            finishedCallback.run();
-                        }
-                    });
-                    mAnimator.start();
+                AnimatorSet anim = new AnimatorSet();
+                anim.play(getClosingWindowAnimators(targetCompats));
 
-                    // Because t=0 has the app icon in its original spot, we can skip the
-                    // first frame and have the same movement one frame earlier.
-                    mAnimator.setCurrentPlayTime(REFRESH_RATE_MS);
-                });
+                if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)) {
+                    AnimatorSet contentAnimation = getLauncherResumeAnimation();
+                    anim.play(contentAnimation);
+
+                    // Only register the content animation for cancellation when state changes
+                    mLauncher.getStateManager().setCurrentAnimation(contentAnimation);
+                }
+                return anim;
             }
         };
     }
@@ -844,16 +856,22 @@
         if (mLauncher.isInState(LauncherState.ALL_APPS)
                 || mLauncher.getDeviceProfile().isVerticalBarLayout()) {
             AnimatorSet contentAnimator = getLauncherContentAnimator(true /* show */);
+            contentAnimator.addListener(mUiResetListener);
             contentAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
             return contentAnimator;
         } else {
             AnimatorSet workspaceAnimator = new AnimatorSet();
+
             mLauncher.getWorkspace().setTranslationY(mWorkspaceTransY);
-            mLauncher.getWorkspace().setAlpha(0f);
             workspaceAnimator.play(ObjectAnimator.ofFloat(mLauncher.getWorkspace(),
                     View.TRANSLATION_Y, mWorkspaceTransY, 0));
-            workspaceAnimator.play(ObjectAnimator.ofFloat(mLauncher.getWorkspace(), View.ALPHA,
-                    0, 1f));
+
+            View currentPage = ((CellLayout) mLauncher.getWorkspace()
+                    .getChildAt(mLauncher.getWorkspace().getCurrentPage()))
+                    .getShortcutsAndWidgets();
+            currentPage.setAlpha(0f);
+            workspaceAnimator.play(ObjectAnimator.ofFloat(currentPage, View.ALPHA, 0, 1f));
+
             workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
             workspaceAnimator.setDuration(333);
             workspaceAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
@@ -881,6 +899,8 @@
             AnimatorSet resumeLauncherAnimation = new AnimatorSet();
             resumeLauncherAnimation.play(workspaceAnimator);
             resumeLauncherAnimation.playSequentially(allAppsSlideIn, allAppsOvershoot);
+
+            resumeLauncherAnimation.addListener(mReapplyStateListener);
             return resumeLauncherAnimation;
         }
     }
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/LauncherTransitionAnimator.java b/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
deleted file mode 100644
index ab9234b..0000000
--- a/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
+++ /dev/null
@@ -1,75 +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.launcher3;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-
-/**
- * Creates an AnimatorSet consisting on one Animator for Launcher transition, and one Animator for
- * the Window transitions.
- *
- * Allows for ending the Launcher animator without ending the Window animator.
- */
-public class LauncherTransitionAnimator {
-
-    private final MutableBoolean mLauncherAnimCancelState;
-
-    private AnimatorSet mAnimatorSet;
-    private Animator mLauncherAnimator;
-    private Animator mWindowAnimator;
-
-    LauncherTransitionAnimator(Animator launcherAnimator, Animator windowAnimator) {
-        this(launcherAnimator, windowAnimator, new MutableBoolean(false));
-    }
-
-
-    LauncherTransitionAnimator(Animator launcherAnimator, Animator windowAnimator,
-            MutableBoolean launcherAnimCancelState) {
-        mLauncherAnimCancelState = launcherAnimCancelState;
-        if (launcherAnimator != null) {
-            mLauncherAnimator = launcherAnimator;
-        }
-        mWindowAnimator = windowAnimator;
-
-        mAnimatorSet = new AnimatorSet();
-        if (launcherAnimator != null) {
-            mAnimatorSet.play(launcherAnimator);
-        }
-        mAnimatorSet.play(windowAnimator);
-    }
-
-    public AnimatorSet getAnimatorSet() {
-        return mAnimatorSet;
-    }
-
-    public void cancel() {
-        mAnimatorSet.cancel();
-        mLauncherAnimCancelState.value = true;
-    }
-
-    public boolean isRunning() {
-        return mAnimatorSet.isRunning();
-    }
-
-    public void finishLauncherAnimation() {
-        if (mLauncherAnimator != null) {
-            mLauncherAnimCancelState.value = true;
-            mLauncherAnimator.end();
-        }
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/MutableBoolean.java b/quickstep/src/com/android/launcher3/MutableBoolean.java
deleted file mode 100644
index 7538217..0000000
--- a/quickstep/src/com/android/launcher3/MutableBoolean.java
+++ /dev/null
@@ -1,25 +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.launcher3;
-
-public class MutableBoolean {
-    public boolean value;
-
-    public MutableBoolean(boolean value) {
-        this.value = value;
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index ea03a76..1fb3584 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -25,6 +25,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
@@ -58,7 +59,8 @@
 
     @Override
     public String getDescription(Launcher launcher) {
-        return launcher.getString(R.string.all_apps_button_label);
+        AllAppsContainerView appsView = launcher.getAppsView();
+        return appsView.getDescription();
     }
 
     @Override
@@ -83,7 +85,12 @@
     }
 
     @Override
-    public float getHoseatAlpha(Launcher launcher) {
+    public int getVisibleElements(Launcher launcher) {
+        return ALL_APPS_HEADER | ALL_APPS_CONTENT;
+    }
+
+    @Override
+    public float getOverviewTranslationX(Launcher launcher) {
         return 0;
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DragPauseDetector.java b/quickstep/src/com/android/launcher3/uioverrides/DragPauseDetector.java
deleted file mode 100644
index 6df1aba..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/DragPauseDetector.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.launcher3.uioverrides;
-
-import com.android.launcher3.Alarm;
-import com.android.launcher3.OnAlarmListener;
-
-/**
- * Utility class to detect a pause during a drag.
- */
-public class DragPauseDetector implements OnAlarmListener {
-
-    private static final float MAX_VELOCITY_TO_PAUSE = 0.2f;
-    private static final long PAUSE_DURATION = 100;
-
-    private final Alarm mAlarm;
-    private final Runnable mOnPauseCallback;
-
-    private boolean mTriggered = false;
-    private int mDisabledFlags = 0;
-
-    public DragPauseDetector(Runnable onPauseCallback) {
-        mOnPauseCallback = onPauseCallback;
-
-        mAlarm = new Alarm();
-        mAlarm.setOnAlarmListener(this);
-        mAlarm.setAlarm(PAUSE_DURATION);
-    }
-
-    public void onDrag(float velocity) {
-        if (mTriggered || !isEnabled()) {
-            return;
-        }
-
-        if (Math.abs(velocity) > MAX_VELOCITY_TO_PAUSE) {
-            // Cancel any previous alarm and set a new alarm
-            mAlarm.setAlarm(PAUSE_DURATION);
-        }
-    }
-
-    @Override
-    public void onAlarm(Alarm alarm) {
-        if (!mTriggered && isEnabled()) {
-            mTriggered = true;
-            mOnPauseCallback.run();
-        }
-    }
-
-    public boolean isTriggered () {
-        return mTriggered;
-    }
-
-    public boolean isEnabled() {
-        return mDisabledFlags == 0;
-    }
-
-    public void addDisabledFlags(int flags) {
-        boolean wasEnabled = isEnabled();
-        mDisabledFlags |= flags;
-        resetAlarm(wasEnabled);
-    }
-
-    public void clearDisabledFlags(int flags) {
-        boolean wasEnabled = isEnabled();
-        mDisabledFlags  &= ~flags;
-        resetAlarm(wasEnabled);
-    }
-
-    private void resetAlarm(boolean wasEnabled) {
-        boolean isEnabled = isEnabled();
-        if (wasEnabled == isEnabled) {
-          // Nothing has changed
-        } if (isEnabled && !mTriggered) {
-            mAlarm.setAlarm(PAUSE_DURATION);
-        } else if (!isEnabled) {
-            mAlarm.cancelAlarm();
-        }
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
deleted file mode 100644
index a55edfe..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
+++ /dev/null
@@ -1,165 +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.launcher3.uioverrides;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.touch.SwipeDetector.DIRECTION_NEGATIVE;
-import static com.android.launcher3.touch.SwipeDetector.DIRECTION_POSITIVE;
-import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
-import static com.android.launcher3.touch.SwipeDetector.VERTICAL;
-import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
-
-import android.graphics.Rect;
-import android.metrics.LogMaker;
-import android.view.MotionEvent;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.util.VerticalSwipeController;
-import com.android.quickstep.RecentsView;
-
-class EventLogTags {
-    private EventLogTags() {
-    }  // don't instantiate
-
-    /** 524292 sysui_multi_action (content|4) */
-    public static final int SYSUI_MULTI_ACTION = 524292;
-
-    public static void writeSysuiMultiAction(Object[] content) {
-        android.util.EventLog.writeEvent(SYSUI_MULTI_ACTION, content);
-    }
-}
-
-class MetricsLogger {
-    private static MetricsLogger sMetricsLogger;
-
-    private static MetricsLogger getLogger() {
-        if (sMetricsLogger == null) {
-            sMetricsLogger = new MetricsLogger();
-        }
-        return sMetricsLogger;
-    }
-
-    protected void saveLog(Object[] rep) {
-        EventLogTags.writeSysuiMultiAction(rep);
-    }
-
-    public void write(LogMaker content) {
-        if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
-            content.setType(4/*MetricsEvent.TYPE_ACTION*/);
-        }
-        saveLog(content.serialize());
-    }
-}
-
-/**
- * Extension of {@link VerticalSwipeController} to go from NORMAL to OVERVIEW.
- */
-public class EdgeSwipeController extends VerticalSwipeController implements
-        OnDeviceProfileChangeListener {
-
-    private static final Rect sTempRect = new Rect();
-
-    private final MetricsLogger mMetricsLogger = new MetricsLogger();
-
-    public EdgeSwipeController(Launcher l) {
-        super(l, NORMAL, OVERVIEW, l.getDeviceProfile().isVerticalBarLayout()
-                ? HORIZONTAL : VERTICAL);
-        l.addOnDeviceProfileChangeListener(this);
-    }
-
-    @Override
-    public void onDeviceProfileChanged(DeviceProfile dp) {
-        mDetector.updateDirection(dp.isVerticalBarLayout() ? HORIZONTAL : VERTICAL);
-    }
-
-    @Override
-    protected boolean shouldInterceptTouch(MotionEvent ev) {
-        return mLauncher.isInState(NORMAL) && (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
-    }
-
-    @Override
-    protected int getSwipeDirection(MotionEvent ev) {
-        return isTransitionFlipped() ? DIRECTION_NEGATIVE : DIRECTION_POSITIVE;
-    }
-
-    public EdgeSwipeController(Launcher l, LauncherState baseState) {
-        super(l, baseState);
-    }
-
-    @Override
-    protected boolean isTransitionFlipped() {
-        return mLauncher.getDeviceProfile().isSeascape();
-    }
-
-    @Override
-    protected void onTransitionComplete(boolean wasFling, boolean stateChanged) {
-        if (stateChanged && mToState instanceof OverviewState) {
-            // Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
-            // "Recents" activity for app transition tests.
-            final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
-            builder.setPackageName("com.android.systemui");
-            builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
-                    "com.android.systemui.recents.RecentsActivity");
-            builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
-                    0/* zero time */);
-            mMetricsLogger.write(builder);
-
-            // Add user event logging for launcher pipeline
-            int direction = Direction.UP;
-            if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-                direction = Direction.LEFT;
-                if (mLauncher.getDeviceProfile().isSeascape()) {
-                    direction = Direction.RIGHT;
-                }
-            }
-            mLauncher.getUserEventDispatcher().logStateChangeAction(
-                    wasFling ? Touch.FLING : Touch.SWIPE, direction,
-                    ContainerType.NAVBAR, ContainerType.WORKSPACE, // src target
-                    ContainerType.TASKSWITCHER,                    // dst target
-                    mLauncher.getWorkspace().getCurrentPage());
-        }
-    }
-
-    @Override
-    protected float getShiftRange() {
-        return getShiftRange(mLauncher);
-    }
-
-    public static float getShiftRange(Launcher launcher) {
-        RecentsView.getPageRect(launcher.getDeviceProfile(), launcher, sTempRect);
-        DragLayer dl = launcher.getDragLayer();
-        Rect insets = dl.getInsets();
-        DeviceProfile dp = launcher.getDeviceProfile();
-
-        if (dp.isVerticalBarLayout()) {
-            if (dp.isSeascape()) {
-                return insets.left + sTempRect.left;
-            } else {
-                return dl.getWidth() - sTempRect.right + insets.right;
-            }
-        } else {
-            return dl.getHeight() - sTempRect.bottom + insets.bottom;
-        }
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
index acd4fc1..9e82b25 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
@@ -16,6 +16,8 @@
 package com.android.launcher3.uioverrides;
 
 import com.android.launcher3.Launcher;
+import com.android.quickstep.QuickScrubController;
+import com.android.quickstep.views.RecentsView;
 
 /**
  * Extension of overview state used for QuickScrub
@@ -23,19 +25,26 @@
 public class FastOverviewState extends OverviewState {
 
     private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_DISABLE_RESTORE
-            | FLAG_PAGE_BACKGROUNDS | FLAG_DISABLE_INTERACTION | FLAG_OVERVIEW_UI;
+            | FLAG_DISABLE_INTERACTION | FLAG_OVERVIEW_UI;
 
     private static final boolean DEBUG_DIFFERENT_UI = false;
 
     public FastOverviewState(int id) {
-        super(id, STATE_FLAGS);
+        super(id, QuickScrubController.QUICK_SWITCH_START_DURATION, STATE_FLAGS);
     }
 
     @Override
-    public float getHoseatAlpha(Launcher launcher) {
+    public void onStateTransitionEnd(Launcher launcher) {
+        super.onStateTransitionEnd(launcher);
+        RecentsView recentsView = launcher.getOverviewPanel();
+        recentsView.getQuickScrubController().onFinishedTransitionToQuickScrub();
+    }
+
+    @Override
+    public int getVisibleElements(Launcher launcher) {
         if (DEBUG_DIFFERENT_UI) {
-            return 0;
+            return NONE;
         }
-        return super.getHoseatAlpha(launcher);
+        return super.getVisibleElements(launcher);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
new file mode 100644
index 0000000..23add95
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
@@ -0,0 +1,71 @@
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
+
+import android.view.MotionEvent;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.touch.AbstractStateChangeTouchController;
+import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.quickstep.util.SysuiEventLogger;
+
+/**
+ * Touch controller for handling edge swipes in landscape/seascape UI
+ */
+public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchController {
+
+    public LandscapeEdgeSwipeController(Launcher l) {
+        super(l, SwipeDetector.HORIZONTAL);
+    }
+
+    @Override
+    protected boolean canInterceptTouch(MotionEvent ev) {
+        if (mCurrentAnimation != null) {
+            // If we are already animating from a previous state, we can intercept.
+            return true;
+        }
+        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+            return false;
+        }
+        return mLauncher.isInState(NORMAL) && (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
+    }
+
+    @Override
+    protected int getSwipeDirection(MotionEvent ev) {
+        mFromState = NORMAL;
+        mToState = OVERVIEW;
+        return SwipeDetector.DIRECTION_BOTH;
+    }
+
+    @Override
+    protected float getShiftRange() {
+        return mLauncher.getDragLayer().getWidth();
+    }
+
+    @Override
+    protected float initCurrentAnimation() {
+        float range = getShiftRange();
+        long maxAccuracy = (long) (2 * range);
+        mCurrentAnimation = mLauncher.getStateManager()
+                .createAnimationToNewWorkspace(mToState, maxAccuracy);
+        return (mLauncher.getDeviceProfile().isSeascape() ? 2 : -2) / range;
+    }
+
+    @Override
+    protected int getDirectionForLog() {
+        return mLauncher.getDeviceProfile().isSeascape() ? Direction.RIGHT : Direction.LEFT;
+    }
+
+    @Override
+    protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
+        super.onSwipeInteractionCompleted(targetState, logAction);
+        if (mFromState == NORMAL && targetState == OVERVIEW) {
+            SysuiEventLogger.writeDummyRecentsTransition(0);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
new file mode 100644
index 0000000..720b20a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
@@ -0,0 +1,70 @@
+/*
+ * 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.launcher3.uioverrides;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+
+import android.view.MotionEvent;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.quickstep.TouchInteractionService;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Touch controller from going from OVERVIEW to ALL_APPS
+ */
+public class LandscapeStatesTouchController extends PortraitStatesTouchController {
+
+    public LandscapeStatesTouchController(Launcher l) {
+        super(l);
+    }
+
+    @Override
+    protected boolean canInterceptTouch(MotionEvent ev) {
+        if (mCurrentAnimation != null) {
+            // If we are already animating from a previous state, we can intercept.
+            return true;
+        }
+        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+            return false;
+        }
+        if (mLauncher.isInState(ALL_APPS)) {
+            // In all-apps only listen if the container cannot scroll itself
+            return mLauncher.getAppsView().shouldContainerScroll(ev);
+        } else if (mLauncher.isInState(NORMAL)) {
+            return true;
+        } else if (mLauncher.isInState(OVERVIEW)) {
+            RecentsView rv = mLauncher.getOverviewPanel();
+            return ev.getY() > (rv.getBottom() - rv.getPaddingBottom());
+        } else {
+            return false;
+        }
+    }
+
+    protected LauncherState getTargetState() {
+        if (mLauncher.isInState(ALL_APPS)) {
+            // Should swipe down go to OVERVIEW instead?
+            return TouchInteractionService.isConnected() ?
+                    mLauncher.getStateManager().getLastState() : NORMAL;
+        } else {
+            return ALL_APPS;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 854fb4f..d123dce 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -16,18 +16,18 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
 
 import android.graphics.Rect;
 import android.view.View;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.quickstep.RecentsView;
+import com.android.quickstep.views.RecentsView;
 
 /**
  * Definition for overview state
@@ -35,31 +35,31 @@
 public class OverviewState extends LauncherState {
 
     private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
-            | FLAG_DISABLE_RESTORE | FLAG_PAGE_BACKGROUNDS | FLAG_OVERVIEW_UI;
+            | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI;
 
     public OverviewState(int id) {
-        this(id, STATE_FLAGS);
+        this(id, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
     }
 
-    protected OverviewState(int id, int stateFlags) {
-        super(id, ContainerType.TASKSWITCHER, OVERVIEW_TRANSITION_MS, stateFlags);
+    protected OverviewState(int id, int transitionDuration, int stateFlags) {
+        super(id, ContainerType.TASKSWITCHER, transitionDuration, stateFlags);
     }
 
     @Override
     public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
         Rect pageRect = new Rect();
-        RecentsView.getScaledDownPageRect(launcher.getDeviceProfile(), launcher, pageRect);
-        RecentsView rv = launcher.getOverviewPanel();
+        RecentsView.getPageRect(launcher.getDeviceProfile(), launcher, pageRect);
 
         if (launcher.getWorkspace().getNormalChildWidth() <= 0 || pageRect.isEmpty()) {
             return super.getWorkspaceScaleAndTranslation(launcher);
         }
 
-        float overlap = 0;
-        if (rv.getCurrentPage() >= rv.getFirstTaskIndex()) {
-            overlap = launcher.getResources().getDimension(R.dimen.workspace_overview_offset_x);
-        }
-        return getScaleAndTranslationForPageRect(launcher, overlap, pageRect);
+        return getScaleAndTranslationForPageRect(launcher, pageRect);
+    }
+
+    @Override
+    public float getOverviewTranslationX(Launcher launcher) {
+        return 0;
     }
 
     @Override
@@ -85,40 +85,50 @@
     }
 
     public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
-        final int centerPage = launcher.getWorkspace().getNextPage();
-        return new PageAlphaProvider(ACCEL_2) {
+        return new PageAlphaProvider(DEACCEL_2) {
             @Override
             public float getPageAlpha(int pageIndex) {
-                return  pageIndex != centerPage ? 0 : 1f;
+                return 0;
             }
         };
     }
 
-    public static float[] getScaleAndTranslationForPageRect(Launcher launcher, float offsetX,
-            Rect pageRect) {
+    public static float[] getScaleAndTranslationForPageRect(Launcher launcher, Rect pageRect) {
         Workspace ws = launcher.getWorkspace();
         float childWidth = ws.getNormalChildWidth();
-        float childHeight = ws.getNormalChildHeight();
 
-        float scale = pageRect.height() / childHeight;
+        float scale = pageRect.width() / childWidth;
         Rect insets = launcher.getDragLayer().getInsets();
 
         float halfHeight = ws.getExpectedHeight() / 2;
         float childTop = halfHeight - scale * (halfHeight - ws.getPaddingTop() - insets.top);
         float translationY = pageRect.top - childTop;
 
-        // Align the workspace horizontally centered with the task rect
-        float halfWidth = ws.getExpectedWidth() / 2;
-        float childCenter = halfWidth -
-                scale * (halfWidth - ws.getPaddingLeft() - insets.left - childWidth / 2);
-        float translationX = pageRect.centerX() - childCenter;
+        return new float[] {scale, 0, translationY};
+    }
 
-        if (launcher.<RecentsView>getOverviewPanel().isRtl()) {
-            translationX -= offsetX / scale;
+    @Override
+    public int getVisibleElements(Launcher launcher) {
+        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
+            // TODO: Remove hotseat from overview
+            return HOTSEAT;
         } else {
-            translationX += offsetX / scale;
+            return launcher.getAppsView().getFloatingHeaderView().hasVisibleContent()
+                    ? ALL_APPS_HEADER : HOTSEAT;
         }
+    }
 
-        return new float[] {scale, translationX, translationY};
+    @Override
+    public float getVerticalProgress(Launcher launcher) {
+        if (getVisibleElements(launcher) == HOTSEAT) {
+            return super.getVerticalProgress(launcher);
+        }
+        return 1 - (getDefaultSwipeHeight(launcher)
+                / launcher.getAllAppsController().getShiftRange());
+    }
+
+    public static float getDefaultSwipeHeight(Launcher launcher) {
+        DeviceProfile dp = launcher.getDeviceProfile();
+        return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeUpController.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeUpController.java
deleted file mode 100644
index 4fb3886..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeUpController.java
+++ /dev/null
@@ -1,71 +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.launcher3.uioverrides;
-
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-import android.view.MotionEvent;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.touch.SwipeDetector;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.util.VerticalSwipeController;
-
-/**
- * Extension of {@link VerticalSwipeController} which allows swipe up from OVERVIEW to ALL_APPS
- * Note that the swipe down is handled by {@link TwoStepSwipeController}.
- */
-public class OverviewSwipeUpController extends VerticalSwipeController {
-
-    public OverviewSwipeUpController(Launcher l) {
-        super(l, OVERVIEW);
-    }
-
-    @Override
-    protected boolean shouldInterceptTouch(MotionEvent ev) {
-        if (!mLauncher.isInState(OVERVIEW)) {
-            return false;
-        }
-        if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-            return ev.getY() >
-                    mLauncher.getDragLayer().getHeight() * OVERVIEW.getVerticalProgress(mLauncher);
-        } else {
-            return mLauncher.getDragLayer().isEventOverHotseat(ev);
-        }
-    }
-
-    @Override
-    protected int getSwipeDirection(MotionEvent ev) {
-        return SwipeDetector.DIRECTION_POSITIVE;
-    }
-
-    @Override
-    protected void onTransitionComplete(boolean wasFling, boolean stateChanged) {
-        if (stateChanged) {
-            // Transition complete. log the action
-            mLauncher.getUserEventDispatcher().logStateChangeAction(
-                    wasFling ? Touch.FLING : Touch.SWIPE,
-                    Direction.UP,
-                    ContainerType.HOTSEAT,
-                    ContainerType.TASKSWITCHER,
-                    ContainerType.ALLAPPS,
-                    mLauncher.getWorkspace().getCurrentPage());
-        }
-
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
new file mode 100644
index 0000000..9f648ed
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -0,0 +1,128 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+
+import android.view.MotionEvent;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.touch.AbstractStateChangeTouchController;
+import com.android.quickstep.TouchInteractionService;
+import com.android.quickstep.util.SysuiEventLogger;
+
+/**
+ * Touch controller for handling various state transitions in portrait UI.
+ */
+public class PortraitStatesTouchController extends AbstractStateChangeTouchController {
+
+    public PortraitStatesTouchController(Launcher l) {
+        super(l, SwipeDetector.VERTICAL);
+    }
+
+    @Override
+    protected boolean canInterceptTouch(MotionEvent ev) {
+        if (mCurrentAnimation != null) {
+            // If we are already animating from a previous state, we can intercept.
+            return true;
+        }
+        if (mLauncher.isInState(ALL_APPS)) {
+            // In all-apps only listen if the container cannot scroll itself
+            if (!mLauncher.getAppsView().shouldContainerScroll(ev)) {
+                return false;
+            }
+        } else {
+            // For all other states, only listen if the event originated below the hotseat height
+            DeviceProfile dp = mLauncher.getDeviceProfile();
+            int hotseatHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
+            if (ev.getY() < (mLauncher.getDragLayer().getHeight() - hotseatHeight)) {
+                return false;
+            }
+        }
+        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    protected int getSwipeDirection(MotionEvent ev) {
+        final int directionsToDetectScroll;
+        if (mLauncher.isInState(ALL_APPS)) {
+            directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
+            mStartContainerType = ContainerType.ALLAPPS;
+        } else if (mLauncher.isInState(NORMAL)) {
+            directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+            mStartContainerType = ContainerType.HOTSEAT;
+        } else if (mLauncher.isInState(OVERVIEW)) {
+            directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+            mStartContainerType = ContainerType.TASKSWITCHER;
+        } else {
+            return 0;
+        }
+        mFromState = mLauncher.getStateManager().getState();
+        mToState = getTargetState();
+        if (mFromState == mToState) {
+            return 0;
+        }
+        return directionsToDetectScroll;
+    }
+
+    protected LauncherState getTargetState() {
+        if (mLauncher.isInState(ALL_APPS)) {
+            // Should swipe down go to OVERVIEW instead?
+            return TouchInteractionService.isConnected() ?
+                    mLauncher.getStateManager().getLastState() : NORMAL;
+        } else if (mLauncher.isInState(OVERVIEW)) {
+            return ALL_APPS;
+        } else {
+            return TouchInteractionService.isConnected() ? OVERVIEW : ALL_APPS;
+        }
+    }
+
+    @Override
+    protected float initCurrentAnimation() {
+        float range = getShiftRange();
+        long maxAccuracy = (long) (2 * range);
+        mCurrentAnimation = mLauncher.getStateManager()
+                .createAnimationToNewWorkspace(mToState, maxAccuracy);
+
+        float startVerticalShift = mFromState.getVerticalProgress(mLauncher) * range;
+        float endVerticalShift = mToState.getVerticalProgress(mLauncher) * range;
+
+        float totalShift = endVerticalShift - startVerticalShift;
+        if (totalShift == 0) {
+            totalShift = Math.signum(mFromState.ordinal - mToState.ordinal)
+                    * OverviewState.getDefaultSwipeHeight(mLauncher);
+        }
+        return 1 / totalShift;
+    }
+
+    @Override
+    protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
+        super.onSwipeInteractionCompleted(targetState, logAction);
+        if (mFromState == NORMAL && targetState == OVERVIEW) {
+            SysuiEventLogger.writeDummyRecentsTransition(0);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 92d071a..b68a3d8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -28,88 +27,80 @@
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.PagedView;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 import com.android.quickstep.AnimatedFloat;
-import com.android.quickstep.RecentsView;
-import com.android.quickstep.TaskView;
+import com.android.quickstep.views.RecentsView;
 
 public class RecentsViewStateController implements StateHandler {
 
     private final Launcher mLauncher;
     private final RecentsView mRecentsView;
-    private final WorkspaceCard mWorkspaceCard;
 
     private final AnimatedFloat mTransitionProgress = new AnimatedFloat(this::onTransitionProgress);
     // The fraction representing the visibility of the RecentsView. This allows delaying the
     // overall transition while the RecentsView is being shown or hidden.
     private final AnimatedFloat mVisibilityMultiplier = new AnimatedFloat(this::onVisibilityProgress);
 
-    private boolean mIsRecentsScrollingToFirstTask;
-
     public RecentsViewStateController(Launcher launcher) {
         mLauncher = launcher;
         mRecentsView = launcher.getOverviewPanel();
-        mRecentsView.setStateController(this);
-
-        mWorkspaceCard = (WorkspaceCard) mRecentsView.getChildAt(0);
-        mWorkspaceCard.setup(launcher);
     }
 
     @Override
     public void setState(LauncherState state) {
-        mWorkspaceCard.setWorkspaceScrollingEnabled(state.overviewUi);
         setVisibility(state.overviewUi);
         setTransitionProgress(state.overviewUi ? 1 : 0);
         if (state.overviewUi) {
-            for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
-                ((TaskView) mRecentsView.getPageAt(i)).resetVisualProperties();
-            }
-            mRecentsView.updateCurveProperties();
+            mRecentsView.resetTaskVisuals();
         }
+        float overviewTranslationX = state.getOverviewTranslationX(mLauncher);
+        int direction = mRecentsView.isRtl() ? -1 : 1;
+        mRecentsView.setTranslationX(overviewTranslationX * direction);
     }
 
     @Override
     public void setStateWithAnimation(final LauncherState toState,
             AnimatorSetBuilder builder, AnimationConfig config) {
-        boolean settingEnabled = Utilities.getPrefs(mLauncher)
-            .getBoolean("pref_scroll_to_first_task_default_true", true);
-        mIsRecentsScrollingToFirstTask = mLauncher.isInState(NORMAL) && toState == OVERVIEW
-                && settingEnabled;
-        // TODO: Instead of animating the workspace translationX, move the contents
-        mWorkspaceCard.setWorkspaceScrollingEnabled(mIsRecentsScrollingToFirstTask);
 
         // Scroll to the workspace card before changing to the NORMAL state.
-        int currPage = mRecentsView.getCurrentPage();
         LauncherState fromState = mLauncher.getStateManager().getState();
+        int currPage = mRecentsView.getCurrentPage();
         if (fromState.overviewUi && toState == NORMAL && currPage != 0 && !config.userControlled) {
             int maxSnapDuration = PagedView.SLOW_PAGE_SNAP_ANIMATION_DURATION;
             int durationPerPage = maxSnapDuration / 10;
             int snapDuration = Math.min(maxSnapDuration, durationPerPage * currPage);
             mRecentsView.snapToPage(0, snapDuration);
-            builder.setStartDelay(snapDuration);
+            // Let the snapping animation play for a bit before we translate off screen.
+            builder.setStartDelay(snapDuration / 4);
         }
 
         ObjectAnimator progressAnim =
                 mTransitionProgress.animateToValue(toState.overviewUi ? 1 : 0);
         progressAnim.setDuration(config.duration);
         progressAnim.setInterpolator(Interpolators.LINEAR);
-        progressAnim.addListener(new AnimationSuccessListener() {
-
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                mWorkspaceCard.setWorkspaceScrollingEnabled(toState.overviewUi);
-                mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
-            }
-        });
         builder.play(progressAnim);
 
         ObjectAnimator visibilityAnim = animateVisibility(toState.overviewUi);
         visibilityAnim.setDuration(config.duration);
         visibilityAnim.setInterpolator(Interpolators.LINEAR);
         builder.play(visibilityAnim);
+
+        int direction = mRecentsView.isRtl() ? -1 : 1;
+        float fromTranslationX = fromState.getOverviewTranslationX(mLauncher) * direction;
+        float toTranslationX = toState.getOverviewTranslationX(mLauncher) * direction;
+        ObjectAnimator translationXAnim = ObjectAnimator.ofFloat(mRecentsView, View.TRANSLATION_X,
+                fromTranslationX, toTranslationX);
+        translationXAnim.setDuration(config.duration);
+        translationXAnim.setInterpolator(Interpolators.ACCEL);
+        if (toState.overviewUi) {
+            translationXAnim.addUpdateListener(valueAnimator -> {
+                // While animating into recents, update the visible task data as needed
+                mRecentsView.loadVisibleTaskData();
+            });
+        }
+        builder.play(translationXAnim);
     }
 
     public void setVisibility(boolean isVisible) {
@@ -145,12 +136,6 @@
 
     private void onTransitionProgress() {
         applyProgress();
-        if (mIsRecentsScrollingToFirstTask) {
-            int scrollForFirstTask = mRecentsView.getScrollForPage(mRecentsView.getFirstTaskIndex());
-            int scrollForPage0 = mRecentsView.getScrollForPage(0);
-            mRecentsView.setScrollX((int) (mTransitionProgress.value * scrollForFirstTask
-                    + (1 - mTransitionProgress.value) * scrollForPage0));
-        }
     }
 
     private void onVisibilityProgress() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaggedAnimatorSetBuilder.java b/quickstep/src/com/android/launcher3/uioverrides/TaggedAnimatorSetBuilder.java
deleted file mode 100644
index 651a753..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/TaggedAnimatorSetBuilder.java
+++ /dev/null
@@ -1,51 +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.launcher3.uioverrides;
-
-import android.animation.Animator;
-import android.util.SparseArray;
-
-import com.android.launcher3.anim.AnimatorSetBuilder;
-
-import java.util.Collections;
-import java.util.List;
-
-public class TaggedAnimatorSetBuilder extends AnimatorSetBuilder {
-
-    /**
-     * Map of the index in {@link #mAnims} to tag. All the animations in {@link #mAnims} starting
-     * from this index correspond to the tag (until a new tag is specified for an index)
-     */
-    private final SparseArray<Object> mTags = new SparseArray<>();
-
-    @Override
-    public void startTag(Object obj) {
-        mTags.put(mAnims.size(), obj);
-    }
-
-    public List<Animator> getAnimationsForTag(Object tag) {
-        int startIndex = mTags.indexOfValue(tag);
-        if (startIndex < 0) {
-            return Collections.emptyList();
-        }
-        int startPos = mTags.keyAt(startIndex);
-
-        int endIndex = startIndex + 1;
-        int endPos = endIndex >= mTags.size() ? mAnims.size() : mTags.keyAt(endIndex);
-
-        return mAnims.subList(startPos, endPos);
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
similarity index 62%
rename from quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
rename to quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index 468a561..d11547d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -15,10 +15,12 @@
  */
 package com.android.launcher3.uioverrides;
 
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -26,30 +28,21 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.touch.SwipeDetector;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.TouchController;
-import com.android.quickstep.RecentsView;
-import com.android.quickstep.TaskView;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import com.android.quickstep.PendingAnimation;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
 
 /**
- * Touch controller for swipe interaction in Overview state
+ * Touch controller for handling task view card swipes
  */
-public class OverviewSwipeController extends AnimatorListenerAdapter
+public class TaskViewTouchController extends AnimatorListenerAdapter
         implements TouchController, SwipeDetector.Listener {
 
     private static final String TAG = "OverviewSwipeController";
@@ -65,20 +58,19 @@
     private final RecentsView mRecentsView;
     private final int[] mTempCords = new int[2];
 
+    private PendingAnimation mPendingAnimation;
     private AnimatorPlaybackController mCurrentAnimation;
     private boolean mCurrentAnimationIsGoingUp;
 
     private boolean mNoIntercept;
-    private boolean mSwipeDownEnabled;
 
     private float mDisplacementShift;
     private float mProgressMultiplier;
     private float mEndDisplacement;
-    private int mStartingTarget;
 
     private TaskView mTaskBeingDragged;
 
-    public OverviewSwipeController(Launcher launcher) {
+    public TaskViewTouchController(Launcher launcher) {
         mLauncher = launcher;
         mRecentsView = launcher.getOverviewPanel();
         mDetector = new SwipeDetector(launcher, this, SwipeDetector.VERTICAL);
@@ -95,15 +87,6 @@
         return mLauncher.isInState(OVERVIEW);
     }
 
-    private boolean isEventOverHotseat(MotionEvent ev) {
-        if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-            return ev.getY() >
-                    mLauncher.getDragLayer().getHeight() * OVERVIEW.getVerticalProgress(mLauncher);
-        } else {
-            return mLauncher.getDragLayer().isEventOverHotseat(ev);
-        }
-    }
-
     @Override
     public void onAnimationCancel(Animator animation) {
         if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
@@ -130,30 +113,15 @@
                 ignoreSlopWhenSettling = true;
             } else {
                 mTaskBeingDragged = null;
-                mSwipeDownEnabled = true;
 
-                int currentPage = mRecentsView.getCurrentPage();
-                if (currentPage == 0) {
-                    // User is on home tile
+                View view = mRecentsView.getChildAt(mRecentsView.getCurrentPage());
+                if (view instanceof TaskView && mLauncher.getDragLayer().isEventOverView(view, ev)) {
+                    // The tile can be dragged down to open the task.
+                    mTaskBeingDragged = (TaskView) view;
                     directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
                 } else {
-                    View view = mRecentsView.getChildAt(currentPage);
-                    if (mLauncher.getDragLayer().isEventOverView(view, ev) &&
-                            view instanceof TaskView) {
-                        // The tile can be dragged down to open the task.
-                        mTaskBeingDragged = (TaskView) view;
-                        directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
-                        mStartingTarget = LauncherLogProto.ItemType.TASK;
-                    } else if (isEventOverHotseat(ev)) {
-                        // The hotseat is being dragged
-                        directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
-                        mSwipeDownEnabled = false;
-                        mStartingTarget = ContainerType.HOTSEAT;
-                    } else {
-                        mNoIntercept = true;
-                        mStartingTarget = ContainerType.WORKSPACE;
-                        return false;
-                    }
+                    mNoIntercept = true;
+                    return false;
                 }
             }
 
@@ -175,9 +143,6 @@
     }
 
     private void reInitAnimationController(boolean goingUp) {
-        if (!goingUp && !mSwipeDownEnabled) {
-            goingUp = true;
-        }
         if (mCurrentAnimation != null && mCurrentAnimationIsGoingUp == goingUp) {
             // No need to init
             return;
@@ -185,44 +150,30 @@
         if (mCurrentAnimation != null) {
             mCurrentAnimation.setPlayFraction(0);
         }
+        if (mPendingAnimation != null) {
+            mPendingAnimation.finish(false);
+            mPendingAnimation = null;
+        }
+
         mCurrentAnimationIsGoingUp = goingUp;
         float range = mLauncher.getAllAppsController().getShiftRange();
         long maxDuration = (long) (2 * range);
         DragLayer dl = mLauncher.getDragLayer();
 
-        if (mTaskBeingDragged == null) {
-            // User is either going to all apps or home
-            mCurrentAnimation = mLauncher.getStateManager()
-                    .createAnimationToNewWorkspace(goingUp ? ALL_APPS : NORMAL, maxDuration);
-            if (goingUp) {
-                mEndDisplacement = -range;
-            } else {
-                mEndDisplacement = EdgeSwipeController.getShiftRange(mLauncher);
-            }
+        if (goingUp) {
+            mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
+                    true /* animateTaskView */, true /* removeTask */, maxDuration);
+            mCurrentAnimation = AnimatorPlaybackController
+                    .wrap(mPendingAnimation.anim, maxDuration);
+            mEndDisplacement = -mTaskBeingDragged.getHeight();
         } else {
-            if (goingUp) {
-                AnimatorSet anim = new AnimatorSet();
-                ObjectAnimator translate = ObjectAnimator.ofFloat(
-                        mTaskBeingDragged, View.TRANSLATION_Y, -mTaskBeingDragged.getBottom());
-                translate.setInterpolator(LINEAR);
-                translate.setDuration(maxDuration);
-                anim.play(translate);
+            AnimatorSet anim = new AnimatorSet();
+            // TODO: Setup a zoom animation
+            mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
 
-                ObjectAnimator alpha = ObjectAnimator.ofFloat(mTaskBeingDragged, View.ALPHA, 0);
-                alpha.setInterpolator(DEACCEL_1_5);
-                alpha.setDuration(maxDuration);
-                anim.play(alpha);
-                mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
-                mEndDisplacement = -mTaskBeingDragged.getBottom();
-            } else {
-                AnimatorSet anim = new AnimatorSet();
-                // TODO: Setup a zoom animation
-                mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
-
-                mTempCords[1] = mTaskBeingDragged.getHeight();
-                dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
-                mEndDisplacement = dl.getHeight() - mTempCords[1];
-            }
+            mTempCords[1] = mTaskBeingDragged.getHeight();
+            dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
+            mEndDisplacement = dl.getHeight() - mTempCords[1];
         }
 
         mCurrentAnimation.getTarget().addListener(this);
@@ -260,9 +211,7 @@
         if (fling) {
             logAction = Touch.FLING;
             boolean goingUp = velocity < 0;
-            if (!goingUp && !mSwipeDownEnabled) {
-                goingToEnd = false;
-            } else if (goingUp != mCurrentAnimationIsGoingUp) {
+            if (goingUp != mCurrentAnimationIsGoingUp) {
                 // In case the fling is in opposite direction, make sure if is close enough
                 // from the start position
                 if (mCurrentAnimation.getProgressFraction()
@@ -288,7 +237,6 @@
         float nextFrameProgress = Utilities.boundToRange(
                 progress + velocity * SINGLE_FRAME_MS / Math.abs(mEndDisplacement), 0f, 1f);
 
-
         mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd, logAction));
 
         ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
@@ -299,27 +247,17 @@
     }
 
     private void onCurrentAnimationEnd(boolean wasSuccess, int logAction) {
-        if (mTaskBeingDragged == null) {
-            LauncherState state = wasSuccess ?
-                    (mCurrentAnimationIsGoingUp ? ALL_APPS : NORMAL) : OVERVIEW;
-            mLauncher.getStateManager().goToState(state, false);
-
-        } else if (wasSuccess) {
-            if (mCurrentAnimationIsGoingUp) {
-                mRecentsView.onTaskDismissed(mTaskBeingDragged);
-            } else {
+        if (mPendingAnimation != null) {
+            mPendingAnimation.finish(wasSuccess);
+            mPendingAnimation = null;
+        }
+        if (wasSuccess) {
+            if (!mCurrentAnimationIsGoingUp) {
                 mTaskBeingDragged.launchTask(false);
                 mLauncher.getUserEventDispatcher().logTaskLaunch(logAction,
                         Direction.DOWN, mTaskBeingDragged.getTask().getTopComponent());
             }
         }
-        if (mTaskBeingDragged == null || (wasSuccess && mCurrentAnimationIsGoingUp)) {
-            mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
-                    mCurrentAnimationIsGoingUp ? Direction.UP : Direction.DOWN,
-                    mStartingTarget, ContainerType.TASKSWITCHER,
-                    mLauncher.getStateManager().getState().containerType,
-                    mRecentsView.getCurrentPage());
-        }
         mDetector.finishedScrolling();
         mTaskBeingDragged = null;
         mCurrentAnimation = null;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
deleted file mode 100644
index c8d75dc..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
+++ /dev/null
@@ -1,447 +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.launcher3.uioverrides;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
-import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.touch.SwipeDetector;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.util.FloatRange;
-import com.android.launcher3.util.TouchController;
-import com.android.quickstep.TouchInteractionService;
-
-/**
- * Handles vertical touch gesture on the DragLayer
- */
-public class TwoStepSwipeController extends AnimatorListenerAdapter
-        implements TouchController, SwipeDetector.Listener {
-
-    private static final String TAG = "TwoStepSwipeController";
-
-    private static final float RECATCH_REJECTION_FRACTION = .0875f;
-    private static final int SINGLE_FRAME_MS = 16;
-    private static final long QUICK_SNAP_TO_OVERVIEW_DURATION = 250;
-
-    // Progress after which the transition is assumed to be a success in case user does not fling
-    private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
-
-    /**
-     * Index of the vertical swipe handles in {@link LauncherStateManager#getStateHandlers()}.
-     */
-    private static final int SWIPE_HANDLER_INDEX = 0;
-
-    /**
-     * Index of various UI handlers in {@link LauncherStateManager#getStateHandlers()} not related
-     * to vertical swipe.
-     */
-    private static final int OTHER_HANDLERS_START_INDEX = SWIPE_HANDLER_INDEX + 1;
-
-    // Swipe progress range (when starting from NORMAL state) where OVERVIEW state is allowed
-    private static final float MIN_PROGRESS_TO_OVERVIEW = 0.1f;
-    private static final float MAX_PROGRESS_TO_OVERVIEW = 0.4f;
-
-    private static final int FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE = 1 << 0;
-    private static final int FLAG_OVERVIEW_DISABLED_FLING = 1 << 1;
-    private static final int FLAG_OVERVIEW_DISABLED_CANCEL_STATE = 1 << 2;
-    private static final int FLAG_OVERVIEW_DISABLED = 1 << 4;
-    private static final int FLAG_DISABLED_TWO_TARGETS = 1 << 5;
-    private static final int FLAG_DISABLED_BACK_TARGET = 1 << 6;
-
-    private final Launcher mLauncher;
-    private final SwipeDetector mDetector;
-
-    private boolean mNoIntercept;
-    private int mStartContainerType;
-
-    private DragPauseDetector mDragPauseDetector;
-    private FloatRange mOverviewProgressRange;
-    private TaggedAnimatorSetBuilder mTaggedAnimatorSetBuilder;
-    private AnimatorSet mQuickOverviewAnimation;
-    private boolean mAnimatingToOverview;
-    private CroppedAnimationController mCroppedAnimationController;
-
-    private AnimatorPlaybackController mCurrentAnimation;
-    private LauncherState mFromState;
-    private LauncherState mToState;
-
-    private float mStartProgress;
-    // Ratio of transition process [0, 1] to drag displacement (px)
-    private float mProgressMultiplier;
-
-    public TwoStepSwipeController(Launcher l) {
-        mLauncher = l;
-        mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
-    }
-
-    private boolean canInterceptTouch(MotionEvent ev) {
-        if (mCurrentAnimation != null) {
-            // If we are already animating from a previous state, we can intercept.
-            return true;
-        }
-        if (mLauncher.isInState(NORMAL)) {
-            if ((ev.getEdgeFlags() & EDGE_NAV_BAR) != 0 &&
-                    !mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-                // On normal swipes ignore edge swipes
-                return false;
-            }
-        } else if (mLauncher.isInState(ALL_APPS)) {
-            if (!mLauncher.getAppsView().shouldContainerScroll(ev)) {
-                return false;
-            }
-        } else {
-            // Don't listen for the swipe gesture if we are already in some other state.
-            return false;
-        }
-        if (mAnimatingToOverview) {
-            return false;
-        }
-        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
-            return false;
-        }
-
-        return true;
-    }
-
-    @Override
-    public void onAnimationCancel(Animator animation) {
-        if (mCurrentAnimation != null && animation == mCurrentAnimation.getOriginalTarget()) {
-            Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
-            clearState();
-        }
-    }
-
-    @Override
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            mNoIntercept = !canInterceptTouch(ev);
-            if (mNoIntercept) {
-                return false;
-            }
-
-            // Now figure out which direction scroll events the controller will start
-            // calling the callbacks.
-            final int directionsToDetectScroll;
-            boolean ignoreSlopWhenSettling = false;
-
-            if (mCurrentAnimation != null) {
-                if (mCurrentAnimation.getProgressFraction() > 1 - RECATCH_REJECTION_FRACTION) {
-                    directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
-                } else if (mCurrentAnimation.getProgressFraction() < RECATCH_REJECTION_FRACTION ) {
-                    directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
-                } else {
-                    directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
-                    ignoreSlopWhenSettling = true;
-                }
-            } else {
-                if (mLauncher.isInState(ALL_APPS)) {
-                    directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
-                    mStartContainerType = ContainerType.ALLAPPS;
-                } else {
-                    directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
-                    mStartContainerType = mLauncher.getDragLayer().isEventOverHotseat(ev) ?
-                            ContainerType.HOTSEAT : ContainerType.WORKSPACE;
-                }
-            }
-
-            mDetector.setDetectableScrollConditions(
-                    directionsToDetectScroll, ignoreSlopWhenSettling);
-        }
-
-        if (mNoIntercept) {
-            return false;
-        }
-
-        onControllerTouchEvent(ev);
-        return mDetector.isDraggingOrSettling();
-    }
-
-    @Override
-    public boolean onControllerTouchEvent(MotionEvent ev) {
-        return mDetector.onTouchEvent(ev);
-    }
-
-    @Override
-    public void onDragStart(boolean start) {
-        if (mCurrentAnimation == null) {
-            float range = getShiftRange();
-            long maxAccuracy = (long) (2 * range);
-
-            mDragPauseDetector = new DragPauseDetector(this::onDragPauseDetected);
-            mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE);
-            if (FeatureFlags.ENABLE_TWO_SWIPE_TARGETS) {
-                mDragPauseDetector.addDisabledFlags(FLAG_DISABLED_TWO_TARGETS);
-            }
-
-            mOverviewProgressRange = new FloatRange();
-            mOverviewProgressRange.start = mLauncher.isInState(NORMAL)
-                    ? MIN_PROGRESS_TO_OVERVIEW
-                    : 1 - MAX_PROGRESS_TO_OVERVIEW;
-            mOverviewProgressRange.end = mOverviewProgressRange.start
-                    + MAX_PROGRESS_TO_OVERVIEW - MIN_PROGRESS_TO_OVERVIEW;
-
-            // Build current animation
-            mFromState = mLauncher.getStateManager().getState();
-            mToState = mLauncher.isInState(ALL_APPS) ? NORMAL : ALL_APPS;
-
-            if (mToState == NORMAL && mLauncher.getStateManager().getLastState() == OVERVIEW) {
-                mToState = OVERVIEW;
-                mDragPauseDetector.addDisabledFlags(FLAG_DISABLED_BACK_TARGET);
-            }
-
-            mTaggedAnimatorSetBuilder = new TaggedAnimatorSetBuilder();
-            mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(
-                    mToState, mTaggedAnimatorSetBuilder, maxAccuracy);
-
-            if (!TouchInteractionService.isConnected()) {
-                mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED);
-            }
-
-            mCurrentAnimation.getTarget().addListener(this);
-            mStartProgress = 0;
-            mProgressMultiplier = (mLauncher.isInState(ALL_APPS) ? 1 : -1) / range;
-            mCurrentAnimation.dispatchOnStart();
-        } else {
-            mCurrentAnimation.pause();
-            mStartProgress = mCurrentAnimation.getProgressFraction();
-
-            mDragPauseDetector.clearDisabledFlags(FLAG_OVERVIEW_DISABLED_FLING);
-            updatePauseDetectorRangeFlag();
-        }
-    }
-
-    private float getShiftRange() {
-        return mLauncher.getAllAppsController().getShiftRange();
-    }
-
-    @Override
-    public boolean onDrag(float displacement, float velocity) {
-        float deltaProgress = mProgressMultiplier * displacement;
-        mCurrentAnimation.setPlayFraction(deltaProgress + mStartProgress);
-
-        updatePauseDetectorRangeFlag();
-        mDragPauseDetector.onDrag(velocity);
-
-        return true;
-    }
-
-    private void updatePauseDetectorRangeFlag() {
-        if (mOverviewProgressRange.contains(mCurrentAnimation.getProgressFraction())) {
-            mDragPauseDetector.clearDisabledFlags(FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE);
-        } else {
-            mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE);
-        }
-    }
-
-    @Override
-    public void onDragEnd(float velocity, boolean fling) {
-        mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_FLING);
-
-        final int logAction;
-        LauncherState targetState;
-        final float progress = mCurrentAnimation.getProgressFraction();
-
-        if (fling) {
-            logAction = Touch.FLING;
-            targetState = velocity < 0 ? ALL_APPS : mLauncher.getStateManager().getLastState();
-            // snap to top or bottom using the release velocity
-        } else {
-            logAction = Touch.SWIPE;
-            targetState = (progress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
-        }
-
-        float endProgress;
-
-        if (mDragPauseDetector.isTriggered() && targetState == NORMAL) {
-            targetState = OVERVIEW;
-            endProgress = OVERVIEW.getVerticalProgress(mLauncher);
-            if (mFromState == NORMAL) {
-                endProgress = 1 - endProgress;
-            }
-        } else if (targetState == mToState) {
-            endProgress = 1;
-        } else {
-            endProgress = 0;
-        }
-
-        LauncherState targetStateFinal = targetState;
-        mCurrentAnimation.setEndAction(() ->
-                onSwipeInteractionCompleted(targetStateFinal, logAction));
-
-        float nextFrameProgress = Utilities.boundToRange(
-                progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
-
-        ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
-        anim.setFloatValues(nextFrameProgress, endProgress);
-        anim.setDuration(
-                SwipeDetector.calculateDuration(velocity, Math.abs(endProgress - progress)));
-        anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
-        anim.start();
-    }
-
-    private void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
-        if (targetState != mFromState) {
-            // Transition complete. log the action
-            mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
-                    mToState == ALL_APPS ? Direction.UP : Direction.DOWN,
-                    mStartContainerType,
-                    mFromState.containerType,
-                    mToState.containerType,
-                    mLauncher.getWorkspace().getCurrentPage());
-        }
-        clearState();
-
-        // TODO: mQuickOverviewAnimation might still be running in which changing a state instantly
-        // may cause a jump. Animate the state change with a short duration in this case?
-        mLauncher.getStateManager().goToState(targetState, false /* animated */);
-    }
-
-    private void onDragPauseDetected() {
-        final ValueAnimator twoStepAnimator = ValueAnimator.ofFloat(0, 1);
-        twoStepAnimator.setDuration(mCurrentAnimation.getDuration());
-        StateHandler[] handlers = mLauncher.getStateManager().getStateHandlers();
-
-        // Change the current animation to only play the vertical handle
-        AnimatorSet anim = new AnimatorSet();
-        anim.playTogether(mTaggedAnimatorSetBuilder.getAnimationsForTag(
-                handlers[SWIPE_HANDLER_INDEX]));
-        anim.play(twoStepAnimator);
-        mCurrentAnimation = mCurrentAnimation.cloneFor(anim);
-
-        AnimatorSetBuilder builder = new AnimatorSetBuilder();
-        AnimationConfig config = new AnimationConfig();
-        config.duration = QUICK_SNAP_TO_OVERVIEW_DURATION;
-        for (int i = OTHER_HANDLERS_START_INDEX; i < handlers.length; i++) {
-            handlers[i].setStateWithAnimation(OVERVIEW, builder, config);
-        }
-        mQuickOverviewAnimation = builder.build();
-        mQuickOverviewAnimation.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                onQuickOverviewAnimationComplete(twoStepAnimator);
-            }
-        });
-        mQuickOverviewAnimation.start();
-    }
-
-    private void onQuickOverviewAnimationComplete(ValueAnimator animator) {
-        if (mAnimatingToOverview) {
-            return;
-        }
-
-        // For the remainder to the interaction, the user can either go to the ALL_APPS state or
-        // the OVERVIEW state.
-        // The remaining state handlers are on the OVERVIEW state. Create one animation towards the
-        // ALL_APPS state and only call it when the user moved above the current range.
-        AnimationConfig config = new AnimationConfig();
-        config.duration = (long) (2 * getShiftRange());
-        config.userControlled = true;
-
-        AnimatorSetBuilder builderToAllAppsState = new AnimatorSetBuilder();
-        StateHandler[] handlers = mLauncher.getStateManager().getStateHandlers();
-        for (int i = OTHER_HANDLERS_START_INDEX; i < handlers.length; i++) {
-            handlers[i].setStateWithAnimation(ALL_APPS, builderToAllAppsState, config);
-        }
-
-        mCroppedAnimationController = new CroppedAnimationController(
-                AnimatorPlaybackController.wrap(builderToAllAppsState.build(), config.duration),
-                new FloatRange(animator.getAnimatedFraction(), mToState == ALL_APPS ? 1 : 0));
-        animator.addUpdateListener(mCroppedAnimationController);
-    }
-
-    private void clearState() {
-        mCurrentAnimation = null;
-        mTaggedAnimatorSetBuilder = null;
-        if (mDragPauseDetector != null) {
-            mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_CANCEL_STATE);
-        }
-        mDragPauseDetector = null;
-
-        if (mQuickOverviewAnimation != null) {
-            mQuickOverviewAnimation.cancel();
-            mQuickOverviewAnimation = null;
-        }
-        mCroppedAnimationController = null;
-        mAnimatingToOverview = false;
-
-        mDetector.finishedScrolling();
-    }
-
-    /**
-     * {@link AnimatorUpdateListener} which controls another animation for a fraction of range
-     */
-    private static class CroppedAnimationController implements AnimatorUpdateListener {
-
-        private final AnimatorPlaybackController mTarget;
-        private final FloatRange mRange;
-
-        CroppedAnimationController(AnimatorPlaybackController target, FloatRange range) {
-            mTarget = target;
-            mRange = range;
-        }
-
-
-        @Override
-        public void onAnimationUpdate(ValueAnimator valueAnimator) {
-            float fraction = valueAnimator.getAnimatedFraction();
-
-            if (mRange.start < mRange.end) {
-                if (fraction <= mRange.start) {
-                    mTarget.setPlayFraction(0);
-                } else if (fraction >= mRange.end) {
-                    mTarget.setPlayFraction(1);
-                } else {
-                    mTarget.setPlayFraction((fraction - mRange.start) / (mRange.end - mRange.start));
-                }
-            } else if (mRange.start > mRange.end) {
-                if (fraction >= mRange.start) {
-                    mTarget.setPlayFraction(0);
-                } else if (fraction <= mRange.end) {
-                    mTarget.setPlayFraction(1);
-                } else {
-                    mTarget.setPlayFraction((fraction - mRange.start) / (mRange.end - mRange.start));
-                }
-            } else {
-                // mRange.start == mRange.end
-                mTarget.setPlayFraction(0);
-            }
-        }
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index a8bcb11..49792ac 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -18,35 +18,33 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 
-import android.graphics.PointF;
+import android.content.Context;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.RecentsModel;
-import com.android.quickstep.RecentsView;
+import com.android.quickstep.views.RecentsView;
 
 public class UiFactory {
 
-    private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
-            "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
-
     public static TouchController[] createTouchControllers(Launcher launcher) {
-        if (FeatureFlags.ENABLE_TWO_SWIPE_TARGETS) {
+        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
             return new TouchController[] {
-                    new EdgeSwipeController(launcher),
-                    new TwoStepSwipeController(launcher),
-                    new OverviewSwipeController(launcher)};
+                    launcher.getDragController(),
+                    new LandscapeStatesTouchController(launcher),
+                    new LandscapeEdgeSwipeController(launcher),
+                    new TaskViewTouchController(launcher)};
         } else {
             return new TouchController[] {
-                    new TwoStepSwipeController(launcher),
-                    new OverviewSwipeController(launcher)};
+                    launcher.getDragController(),
+                    new PortraitStatesTouchController(launcher),
+                    new TaskViewTouchController(launcher)};
         }
     }
 
@@ -60,10 +58,6 @@
                 new RecentsViewStateController(launcher)};
     }
 
-    public static void onWorkspaceLongPress(Launcher launcher, PointF touchPoint) {
-        OptionsPopupView.show(launcher, touchPoint.x, touchPoint.y);
-    }
-
     public static void onLauncherStateOrFocusChanged(Launcher launcher) {
         boolean shouldBackButtonBeVisible = launcher == null
                 || !launcher.isInState(NORMAL)
@@ -79,7 +73,8 @@
                 }
             }
         }
-        OverviewInteractionState.setBackButtonVisible(launcher, shouldBackButtonBeVisible);
+        OverviewInteractionState.getInstance(launcher)
+                .setBackButtonVisible(shouldBackButtonBeVisible);
     }
 
     public static void resetOverview(Launcher launcher) {
@@ -87,15 +82,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/launcher3/uioverrides/WorkspaceCard.java b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
deleted file mode 100644
index 8533502..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
+++ /dev/null
@@ -1,127 +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.launcher3.uioverrides;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.Workspace;
-import com.android.quickstep.RecentsView;
-import com.android.quickstep.RecentsView.PageCallbacks;
-import com.android.quickstep.RecentsView.ScrollState;
-
-public class WorkspaceCard extends View implements PageCallbacks, OnClickListener {
-
-    private final Rect mTempRect = new Rect();
-
-    private Launcher mLauncher;
-    private Workspace mWorkspace;
-
-    private float mLinearInterpolationForPage2 = 1;
-    private float mTranslateXPage0, mTranslateXPage1;
-    private float mExtraScrollShift;
-
-    private boolean mIsWorkspaceScrollingEnabled;
-
-    public WorkspaceCard(Context context) {
-        this(context, null);
-    }
-
-    public WorkspaceCard(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public WorkspaceCard(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        setOnClickListener(this);
-    }
-
-    /**
-     * Draw nothing.
-     */
-    @Override
-    public void draw(Canvas canvas) { }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-
-        // Initiate data
-        mLinearInterpolationForPage2 = RecentsView.getScaledDownPageRect(
-                mLauncher.getDeviceProfile(), mLauncher, mTempRect);
-
-        float[] scale = OverviewState.getScaleAndTranslationForPageRect(mLauncher, 0, mTempRect);
-        mTranslateXPage0 = scale[1];
-        mTranslateXPage1 = OverviewState
-                .getScaleAndTranslationForPageRect(mLauncher,
-                        getResources().getDimension(R.dimen.workspace_overview_offset_x) / scale[0],
-                        mTempRect)[1];
-
-        mExtraScrollShift = 0;
-        if (mWorkspace != null && getWidth() > 0) {
-            float workspaceWidth = mWorkspace.getNormalChildWidth() * scale[0];
-            mExtraScrollShift = (workspaceWidth - getWidth()) / 2;
-            setScaleX(workspaceWidth / getWidth());
-        }
-    }
-
-    @Override
-    public void onClick(View view) {
-        mLauncher.getStateManager().goToState(NORMAL);
-    }
-
-    public void setup(Launcher launcher) {
-        mLauncher = launcher;
-        mWorkspace = mLauncher.getWorkspace();
-    }
-
-    public void setWorkspaceScrollingEnabled(boolean isEnabled) {
-        mIsWorkspaceScrollingEnabled = isEnabled;
-    }
-
-    @Override
-    public int onPageScroll(ScrollState scrollState) {
-        float factor = scrollState.linearInterpolation;
-        float translateX = scrollState.distanceFromScreenCenter;
-        if (mIsWorkspaceScrollingEnabled) {
-            float shift = factor * (mTranslateXPage1 - mTranslateXPage0);
-            mWorkspace.setTranslationX(shift + mTranslateXPage0);
-            translateX += shift;
-        }
-
-        setTranslationX(translateX);
-
-        // If the workspace card is still the first page, shift all the other pages.
-        if (scrollState.linearInterpolation > mLinearInterpolationForPage2) {
-            scrollState.prevPageExtraWidth = 0;
-        } else if (mLinearInterpolationForPage2 > 0) {
-            scrollState.prevPageExtraWidth = mExtraScrollShift *
-                    (1 - scrollState.linearInterpolation / mLinearInterpolationForPage2);
-        } else {
-            scrollState.prevPageExtraWidth = mExtraScrollShift;
-        }
-        return SCROLL_TYPE_WORKSPACE;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
new file mode 100644
index 0000000..b43352a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -0,0 +1,312 @@
+/*
+ * 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) {
+                // Since the launcher is not visible, we can safely reset the scroll position.
+                // This ensures then the next swipe up to all-apps starts from scroll 0.
+                activity.getAppsView().reset(false /* animate */);
+                activity.getStateManager().goToState(OVERVIEW, false);
+
+                // Optimization, hide the all apps view to prevent layout while initializing
+                activity.getAppsView().getContentView().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) {
+            AllAppsTransitionController controller = activity.getAllAppsController();
+            AnimatorSet anim = new AnimatorSet();
+            if (activity.getDeviceProfile().isVerticalBarLayout()) {
+                // TODO:
+            } else {
+                float scrollRange = Math.max(controller.getShiftRange(), 1);
+                float progressDelta = (transitionLength / scrollRange);
+
+                float endProgress = OVERVIEW.getVerticalProgress(activity);
+                float startProgress = endProgress + progressDelta;
+                ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(
+                        controller, ALL_APPS_PROGRESS, startProgress, endProgress);
+                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/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
index 214b3f3..84dfa45 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -77,6 +77,12 @@
         }
     }
 
+    public void finishAnimation() {
+        if (mValueAnimator != null && mValueAnimator.isRunning()) {
+            mValueAnimator.end();
+        }
+    }
+
     public ObjectAnimator getCurrentAnimation() {
         return mValueAnimator;
     }
diff --git a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
deleted file mode 100644
index b3ebd77..0000000
--- a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
+++ /dev/null
@@ -1,49 +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;
-
-    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/DeferredTouchConsumer.java b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
new file mode 100644
index 0000000..b92678a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
@@ -0,0 +1,99 @@
+/*
+ * 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 android.view.Choreographer;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+/**
+ * A TouchConsumer which defers all events on the UIThread until the consumer is created.
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public class DeferredTouchConsumer implements TouchConsumer {
+
+    private final VelocityTracker mVelocityTracker;
+    private final DeferredTouchProvider mTouchProvider;
+
+    private MotionEventQueue mMyQueue;
+    private TouchConsumer mTarget;
+
+    public DeferredTouchConsumer(DeferredTouchProvider touchProvider) {
+        mVelocityTracker = VelocityTracker.obtain();
+        mTouchProvider = touchProvider;
+    }
+
+    @Override
+    public void accept(MotionEvent event) {
+        mTarget.accept(event);
+    }
+
+    @Override
+    public void reset() {
+        mTarget.reset();
+    }
+
+    @Override
+    public void updateTouchTracking(int interactionType) {
+        mTarget.updateTouchTracking(interactionType);
+    }
+
+    @Override
+    public void onQuickScrubEnd() {
+        mTarget.onQuickScrubEnd();
+    }
+
+    @Override
+    public void onQuickScrubProgress(float progress) {
+        mTarget.onQuickScrubProgress(progress);
+    }
+
+    @Override
+    public void preProcessMotionEvent(MotionEvent ev) {
+        mVelocityTracker.addMovement(ev);
+    }
+
+    @Override
+    public Choreographer getIntrimChoreographer(MotionEventQueue queue) {
+        mMyQueue = queue;
+        return null;
+    }
+
+    @Override
+    public void deferInit() {
+        mTarget = mTouchProvider.createTouchConsumer(mVelocityTracker);
+        mTarget.getIntrimChoreographer(mMyQueue);
+    }
+
+    @Override
+    public boolean forceToLauncherConsumer() {
+        return mTarget.forceToLauncherConsumer();
+    }
+
+    @Override
+    public boolean deferNextEventToMainThread() {
+        // If our target is still null, defer the next target as well
+        TouchConsumer target = mTarget;
+        return target == null ? true : target.deferNextEventToMainThread();
+    }
+
+    public interface DeferredTouchProvider {
+
+        TouchConsumer createTouchConsumer(VelocityTracker tracker);
+    }
+}
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
new file mode 100644
index 0000000..032d753
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/FallbackRecentsView.java
@@ -0,0 +1,68 @@
+/*
+ * 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.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
+import com.android.quickstep.views.RecentsView;
+
+public class FallbackRecentsView extends RecentsView<RecentsActivity> implements Insettable {
+
+    public FallbackRecentsView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setOverviewStateEnabled(true);
+    }
+
+    @Override
+    protected void onAllTasksRemoved() {
+        mActivity.finish();
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+        DeviceProfile dp = mActivity.getDeviceProfile();
+        Rect padding = getPadding(dp, getContext());
+        verticalCenter(padding, dp);
+        setPadding(padding.left, padding.top, padding.right, padding.bottom);
+    }
+
+    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/InstantAppResolverImpl.java b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
new file mode 100644
index 0000000..12757c0
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
@@ -0,0 +1,77 @@
+/*
+ * 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.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.InstantAppInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.util.InstantAppResolver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of InstantAppResolver using platform APIs
+ */
+@SuppressWarnings("unused")
+public class InstantAppResolverImpl extends InstantAppResolver {
+
+    private static final String TAG = "InstantAppResolverImpl";
+    public static final String COMPONENT_CLASS_MARKER = "@instantapp";
+
+    private final PackageManager mPM;
+
+    public InstantAppResolverImpl(Context context)
+            throws NoSuchMethodException, ClassNotFoundException {
+        mPM = context.getPackageManager();
+    }
+
+    @Override
+    public boolean isInstantApp(ApplicationInfo info) {
+        return info.isInstantApp();
+    }
+
+    @Override
+    public boolean isInstantApp(AppInfo info) {
+        ComponentName cn = info.getTargetComponent();
+        return cn != null && cn.getClassName().equals(COMPONENT_CLASS_MARKER);
+    }
+
+    @Override
+    public List<ApplicationInfo> getInstantApps() {
+        try {
+            List<ApplicationInfo> result = new ArrayList<>();
+            for (InstantAppInfo iai : mPM.getInstantApps()) {
+                ApplicationInfo info = iai.getApplicationInfo();
+                if (info != null) {
+                    result.add(info);
+                }
+            }
+            return result;
+        } catch (SecurityException se) {
+            Log.w(TAG, "getInstantApps failed. Launcher may not be the default home app.", se);
+        } catch (Exception e) {
+            Log.e(TAG, "Error calling API: getInstantApps", e);
+        }
+        return super.getInstantApps();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/LauncherSearchIndexablesProvider.java b/quickstep/src/com/android/quickstep/LauncherSearchIndexablesProvider.java
new file mode 100644
index 0000000..f5e1f6e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/LauncherSearchIndexablesProvider.java
@@ -0,0 +1,96 @@
+/*
+ * 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.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ResolveInfo;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.os.Build;
+import android.provider.SearchIndexablesContract.XmlResource;
+import android.provider.SearchIndexablesProvider;
+import android.util.Xml;
+
+import com.android.launcher3.R;
+import com.android.launcher3.graphics.IconShapeOverride;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS;
+import static android.provider.SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS;
+import static android.provider.SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS;
+
+@TargetApi(Build.VERSION_CODES.O)
+public class LauncherSearchIndexablesProvider extends SearchIndexablesProvider {
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor queryXmlResources(String[] strings) {
+        MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);
+        ResolveInfo settingsActivity = getContext().getPackageManager().resolveActivity(
+                new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
+                        .setPackage(getContext().getPackageName()), 0);
+        cursor.newRow()
+                .add(XmlResource.COLUMN_XML_RESID, R.xml.indexable_launcher_prefs)
+                .add(XmlResource.COLUMN_INTENT_ACTION, Intent.ACTION_APPLICATION_PREFERENCES)
+                .add(XmlResource.COLUMN_INTENT_TARGET_PACKAGE, getContext().getPackageName())
+                .add(XmlResource.COLUMN_INTENT_TARGET_CLASS, settingsActivity.activityInfo.name);
+        return cursor;
+    }
+
+    @Override
+    public Cursor queryRawData(String[] projection) {
+        return new MatrixCursor(INDEXABLES_RAW_COLUMNS);
+    }
+
+    @Override
+    public Cursor queryNonIndexableKeys(String[] projection) {
+        MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS);
+        if (!getContext().getSystemService(LauncherApps.class).hasShortcutHostPermission()) {
+            // We are not the current launcher. Hide all preferences
+            try (XmlResourceParser parser = getContext().getResources()
+                    .getXml(R.xml.indexable_launcher_prefs)) {
+                final int depth = parser.getDepth();
+                final int[] attrs = new int[] { android.R.attr.key };
+                int type;
+                while (((type = parser.next()) != XmlPullParser.END_TAG ||
+                        parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+                    if (type == XmlPullParser.START_TAG) {
+                        TypedArray a = getContext().obtainStyledAttributes(
+                                Xml.asAttributeSet(parser), attrs);
+                        cursor.addRow(new String[] {a.getString(0)});
+                        a.recycle();
+                    }
+                }
+            } catch (IOException |XmlPullParserException e) {
+                throw new RuntimeException(e);
+            }
+        } else if (!IconShapeOverride.isSupported(getContext())) {
+            cursor.addRow(new String[] {IconShapeOverride.KEY_PREFERENCE});
+        }
+        return cursor;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java
index 6e92d83..94b6faa 100644
--- a/quickstep/src/com/android/quickstep/MotionEventQueue.java
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -53,6 +53,8 @@
             ACTION_VIRTUAL | (4 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_RESET =
             ACTION_VIRTUAL | (5 << ACTION_POINTER_INDEX_SHIFT);
+    private static final int ACTION_DEFER_INIT =
+            ACTION_VIRTUAL | (6 << ACTION_POINTER_INDEX_SHIFT);
 
     private final EventArray mEmptyArray = new EventArray();
     private final Object mExecutionLock = new Object();
@@ -76,10 +78,10 @@
     public MotionEventQueue(Choreographer choreographer, TouchConsumer consumer) {
         mMainChoreographer = choreographer;
         mConsumer = consumer;
-
         mCurrentChoreographer = mMainChoreographer;
         mCurrentRunnable = mMainFrameCallback;
-        setInterimChoreographerLocked(consumer.getIntrimChoreographer(this));
+
+        setInterimChoreographer(consumer.getIntrimChoreographer(this));
     }
 
     public void setInterimChoreographer(Choreographer choreographer) {
@@ -156,6 +158,9 @@
                         case ACTION_RESET:
                             mConsumer.reset();
                             break;
+                        case ACTION_DEFER_INIT:
+                            mConsumer.deferInit();
+                            break;
                         default:
                             Log.e(TAG, "Invalid virtual event: " + event.getAction());
                     }
@@ -204,6 +209,14 @@
         queueVirtualAction(ACTION_RESET, 0);
     }
 
+    public void deferInit() {
+        queueVirtualAction(ACTION_DEFER_INIT, 0);
+    }
+
+    public TouchConsumer getConsumer() {
+        return mConsumer;
+    }
+
     private static class EventArray extends ArrayList<MotionEvent> {
 
         public int lastEventAction = ACTION_CANCEL;
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
deleted file mode 100644
index ff7d434..0000000
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ /dev/null
@@ -1,392 +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_SCRUB;
-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.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) {
-        ObjectAnimator anim = mCurrentShift.animateToValue(progress).setDuration(duration);
-        anim.setInterpolator(Interpolators.SCROLL);
-        anim.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0)
-                        ? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS);
-            }
-        });
-        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/NormalizedIconLoader.java b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
index 431fb30..f875bb7 100644
--- a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
+++ b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
@@ -16,19 +16,20 @@
 package com.android.quickstep;
 
 import android.annotation.TargetApi;
+import android.app.ActivityManager.TaskDescription;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
-import android.os.Build.VERSION_CODES;
 import android.os.UserHandle;
 import android.util.LruCache;
 import android.util.SparseArray;
 
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.graphics.BitmapInfo;
+import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.systemui.shared.recents.model.IconLoader;
 import com.android.systemui.shared.recents.model.TaskKeyLruCache;
@@ -40,11 +41,13 @@
 public class NormalizedIconLoader extends IconLoader {
 
     private final SparseArray<BitmapInfo> mDefaultIcons = new SparseArray<>();
+    private final DrawableFactory mDrawableFactory;
     private LauncherIcons mLauncherIcons;
 
     public NormalizedIconLoader(Context context, TaskKeyLruCache<Drawable> iconCache,
             LruCache<ComponentName, ActivityInfo> activityInfoCache) {
         super(context, iconCache, activityInfoCache);
+        mDrawableFactory = DrawableFactory.get(context);
     }
 
     @Override
@@ -53,7 +56,7 @@
             BitmapInfo info = mDefaultIcons.get(userId);
             if (info == null) {
                 info = getBitmapInfo(Resources.getSystem()
-                        .getDrawable(android.R.drawable.sym_def_app_icon), userId);
+                        .getDrawable(android.R.drawable.sym_def_app_icon), userId, 0, false);
                 mDefaultIcons.put(userId, info);
             }
 
@@ -62,23 +65,31 @@
     }
 
     @Override
-    protected Drawable createBadgedDrawable(Drawable drawable, int userId) {
-        return new FastBitmapDrawable(getBitmapInfo(drawable, userId));
+    protected Drawable createBadgedDrawable(Drawable drawable, int userId, TaskDescription desc) {
+        return new FastBitmapDrawable(getBitmapInfo(drawable, userId, desc.getPrimaryColor(),
+                false));
     }
 
-    private synchronized BitmapInfo getBitmapInfo(Drawable drawable, int userId) {
+    private synchronized BitmapInfo getBitmapInfo(Drawable drawable, int userId,
+            int primaryColor, boolean isInstantApp) {
         if (mLauncherIcons == null) {
             mLauncherIcons = LauncherIcons.obtain(mContext);
         }
 
+        mLauncherIcons.setWrapperBackgroundColor(primaryColor);
         // User version code O, so that the icon is always wrapped in an adaptive icon container.
         return mLauncherIcons.createBadgedIconBitmap(drawable, UserHandle.of(userId),
-                Build.VERSION_CODES.O);
+                Build.VERSION_CODES.O, isInstantApp);
     }
 
     @Override
-    protected Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId) {
-        return createBadgedDrawable(
-                activityInfo.loadUnbadgedIcon(mContext.getPackageManager()), userId);
+    protected Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId,
+            TaskDescription desc) {
+        BitmapInfo bitmapInfo = getBitmapInfo(
+                activityInfo.loadUnbadgedIcon(mContext.getPackageManager()),
+                userId,
+                desc.getPrimaryColor(),
+                activityInfo.applicationInfo.isInstantApp());
+        return mDrawableFactory.newIcon(bitmapInfo, activityInfo);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 73cd503..ab19c6e 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -22,21 +22,17 @@
 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.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
+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;
 import android.os.Bundle;
 import android.os.Looper;
 import android.util.Log;
@@ -49,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;
@@ -61,51 +55,55 @@
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
+import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 /**
  * Touch consumer for handling events originating from an activity other than Launcher
  */
+@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 = 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;
 
+    private final boolean mIsDeferredDownTarget;
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
     private int mActivePointerId = INVALID_POINTER_ID;
     private boolean mTouchThresholdCrossed;
     private int mTouchSlop;
     private float mStartDisplacement;
-    private BaseSwipeInteractionHandler mInteractionHandler;
+    private WindowTransformSwipeHandler mInteractionHandler;
     private int mDisplayRotation;
     private Rect mStableInsets = new Rect();
-    private @HitTarget int mDownHitTarget = HIT_TARGET_NONE;
 
     private VelocityTracker mVelocityTracker;
     private MotionEventQueue mEventQueue;
+    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) {
+            @HitTarget int downHitTarget, VelocityTracker velocityTracker) {
         super(base);
         mRunningTask = runningTaskInfo;
         mRecentsModel = recentsModel;
         mHomeIntent = homeIntent;
-        mVelocityTracker = VelocityTracker.obtain();
-        mISystemUiProxy = systemUiProxy;
+        mVelocityTracker = velocityTracker;
+        mActivityControlHelper = activityControl;
         mMainThreadExecutor = mainThreadExecutor;
         mBackgroundThreadChoreographer = backgroundThreadChoreographer;
-        mDownHitTarget = downHitTarget;
+        mIsDeferredDownTarget = Arrays.binarySearch(DEFERRED_HIT_TARGETS, downHitTarget) >= 0;
     }
 
     @Override
@@ -124,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() && mDownHitTarget != HIT_TARGET_BACK) {
+                if (!mIsDeferredDownTarget) {
                     startTouchTrackingForWindowAnimation(ev.getEventTime());
                 }
 
@@ -164,14 +162,11 @@
                     if (mTouchThresholdCrossed) {
                         mStartDisplacement = Math.signum(displacement) * mTouchSlop;
 
-                        if (isUsingScreenShot()) {
-                            startTouchTrackingForScreenshotAnimation();
-                        } else if (mDownHitTarget == HIT_TARGET_BACK) {
+                        if (mIsDeferredDownTarget) {
                             // If we deferred starting the window animation on touch down, then
                             // start tracking now
                             startTouchTrackingForWindowAnimation(ev.getEventTime());
                         }
-
                         notifyGestureStarted();
                     }
                 } else if (mInteractionHandler != null) {
@@ -197,11 +192,6 @@
         }
         // Notify the handler that the gesture has actually started
         mInteractionHandler.onGestureStarted();
-
-        // Notify the system that we have started tracking the event
-        if (mISystemUiProxy != null) {
-            executeSafely(mISystemUiProxy::onRecentsAnimationStarted);
-        }
     }
 
     private boolean isNavBarOnRight() {
@@ -212,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);
@@ -291,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) {
@@ -321,7 +251,7 @@
                             handler.onRecentsAnimationCanceled();
                         }
                     }
-                }, null, null);
+                });
 
         if (Looper.myLooper() != Looper.getMainLooper()) {
             startActivity.run();
@@ -350,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();
@@ -367,8 +297,9 @@
     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);
         }
     }
@@ -376,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
@@ -423,4 +345,15 @@
            }
         }
     }
+
+    @Override
+    public boolean forceToLauncherConsumer() {
+        return mIsGoingToHome;
+    }
+
+    @Override
+    public boolean deferNextEventToMainThread() {
+        // TODO: Consider also check if the eventQueue is using mainThread of not.
+        return mInteractionHandler != null;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
new file mode 100644
index 0000000..38c25a3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -0,0 +1,170 @@
+/*
+ * 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.OVERVIEW;
+
+import android.annotation.TargetApi;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.AbstractFloatingView;
+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;
+
+/**
+ * Helper class to handle various atomic commands for switching between Overview.
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public class OverviewCommandHelper extends InternalStateHandler {
+
+    private static final boolean DEBUG_START_FALLBACK_ACTIVITY = false;
+
+    private final Context mContext;
+    private final ActivityManagerWrapper mAM;
+
+    public final Intent homeIntent;
+    public final ComponentName launcher;
+
+    private long mLastToggleTime;
+
+    public OverviewCommandHelper(Context context) {
+        mContext = context;
+        mAM = ActivityManagerWrapper.getInstance();
+
+        homeIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setPackage(context.getPackageName())
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        ResolveInfo info = context.getPackageManager().resolveActivity(homeIntent, 0);
+
+        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);
+    }
+
+    private void openRecents() {
+        Intent intent = addToIntent(new Intent(homeIntent));
+        mContext.startActivity(intent);
+        initWhenReady();
+    }
+
+    public void onOverviewToggle() {
+        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();
+
+                    if (isOverviewAlmostVisible()) {
+                        boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
+                        startNonLauncherTask(isQuickTap ? 2 : 1);
+                    } else {
+                        openRecents();
+                    }
+                }
+        );
+    }
+
+    public void onOverviewShown() {
+        getLauncher().runOnUiThread(() -> {
+                    if (isOverviewAlmostVisible()) {
+                        final RecentsView rv = getLauncher().getOverviewPanel();
+                        rv.snapToTaskAfterNext();
+                    } else {
+                        openRecents();
+                    }
+                }
+        );
+    }
+
+    public void onOverviewHidden() {
+        getLauncher().runOnUiThread(() -> {
+                    final RecentsView rv = getLauncher().getOverviewPanel();
+                    rv.launchNextTask();
+                }
+        );
+    }
+
+    private void startNonLauncherTask(int backStackCount) {
+        for (RecentTaskInfo rti : mAM.getRecentTasks(backStackCount, UserHandle.myUserId())) {
+            backStackCount--;
+            if (backStackCount == 0) {
+                mAM.startActivityFromRecents(rti.id, null);
+                break;
+            }
+        }
+    }
+
+    private boolean isOverviewAlmostVisible() {
+        if (clearReference()) {
+            return true;
+        }
+        if (!mAM.getRunningTask().topActivity.equals(launcher)) {
+            return false;
+        }
+        Launcher launcher = getLauncher();
+        return launcher != null && launcher.isStarted() && launcher.isInState(OVERVIEW);
+    }
+
+    private Launcher getLauncher() {
+        return (Launcher) LauncherAppState.getInstance(mContext).getModel().getCallback();
+    }
+
+    @Override
+    protected boolean init(Launcher launcher, boolean alreadyOnHome) {
+        AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
+        launcher.getStateManager().goToState(OVERVIEW, alreadyOnHome);
+        clearReference();
+        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 3c68281..522a883 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,18 +15,28 @@
  */
 package com.android.quickstep;
 
+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:
  *
@@ -37,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;
-
-    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/PendingAnimation.java b/quickstep/src/com/android/quickstep/PendingAnimation.java
new file mode 100644
index 0000000..d22ef61
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/PendingAnimation.java
@@ -0,0 +1,53 @@
+/*
+ * 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.animation.AnimatorSet;
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Utility class to keep track of a running animation.
+ *
+ * This class allows attaching end callbacks to an animation is intended to be used with
+ * {@link com.android.launcher3.anim.AnimatorPlaybackController}, since in that case
+ * AnimationListeners are not properly dispatched.
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class PendingAnimation {
+
+    private final ArrayList<Consumer<Boolean>> mEndListeners = new ArrayList<>();
+
+    public final AnimatorSet anim;
+
+    public PendingAnimation(AnimatorSet anim) {
+        this.anim = anim;
+    }
+
+    public void finish(boolean isSuccess) {
+        for (Consumer<Boolean> listeners : mEndListeners) {
+            listeners.accept(isSuccess);
+        }
+        mEndListeners.clear();
+    }
+
+    public void addEndListener(Consumer<Boolean> listener) {
+        mEndListeners.add(listener);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index f28d51c..d263fbf 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -19,13 +19,14 @@
 import android.view.HapticFeedbackConstants;
 
 import com.android.launcher3.Alarm;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseActivity;
 import com.android.launcher3.OnAlarmListener;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
 
 /**
  * Responds to quick scrub callbacks to page through and launch recent tasks.
@@ -35,8 +36,7 @@
  */
 public class QuickScrubController implements OnAlarmListener {
 
-    public static final int QUICK_SWITCH_START_DURATION = 133;
-    public static final int QUICK_SWITCH_SNAP_DURATION = 120;
+    public static final int QUICK_SWITCH_START_DURATION = 210;
 
     private static final boolean ENABLE_AUTO_ADVANCE = true;
     private static final int NUM_QUICK_SCRUB_SECTIONS = 3;
@@ -47,15 +47,17 @@
 
     private final Alarm mAutoAdvanceAlarm;
     private final RecentsView mRecentsView;
-    private final Launcher mLauncher;
+    private final BaseActivity mActivity;
 
     private boolean mInQuickScrub;
     private int mQuickScrubSection;
-    private int mStartPage;
+    private boolean mStartedFromHome;
     private boolean mHasAlarmRun;
+    private boolean mQuickSwitched;
+    private boolean mFinishedTransitionToQuickScrub;
 
-    public QuickScrubController(Launcher launcher, RecentsView recentsView) {
-        mLauncher = launcher;
+    public QuickScrubController(BaseActivity activity, RecentsView recentsView) {
+        mActivity = activity;
         mRecentsView = recentsView;
         if (ENABLE_AUTO_ADVANCE) {
             mAutoAdvanceAlarm = new Alarm();
@@ -65,10 +67,14 @@
 
     public void onQuickScrubStart(boolean startingFromHome) {
         mInQuickScrub = true;
-        mStartPage = startingFromHome ? 0 : mRecentsView.getFirstTaskIndex();
+        mStartedFromHome = startingFromHome;
         mQuickScrubSection = 0;
         mHasAlarmRun = false;
-        mLauncher.getUserEventDispatcher().resetActionDurationMillis();
+        mQuickSwitched = false;
+        mFinishedTransitionToQuickScrub = false;
+
+        snapToNextTaskIfAvailable();
+        mActivity.getUserEventDispatcher().resetActionDurationMillis();
     }
 
     public void onQuickScrubEnd() {
@@ -78,10 +84,9 @@
         }
         int page = mRecentsView.getNextPage();
         Runnable launchTaskRunnable = () -> {
-            if (page < mRecentsView.getFirstTaskIndex()) {
-                mRecentsView.getPageAt(page).performClick();
-            } else {
-                ((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())
@@ -93,8 +98,8 @@
             // No page move needed, just launch it
             launchTaskRunnable.run();
         }
-        mLauncher.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
-                ControlType.QUICK_SCRUB_BUTTON, null, mStartPage == 0 ?
+        mActivity.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
+                ControlType.QUICK_SCRUB_BUTTON, null, mStartedFromHome ?
                         ContainerType.WORKSPACE : ContainerType.APP);
     }
 
@@ -102,7 +107,9 @@
         int quickScrubSection = Math.round(progress * NUM_QUICK_SCRUB_SECTIONS);
         if (quickScrubSection != mQuickScrubSection) {
             int pageToGoTo = mRecentsView.getNextPage() + quickScrubSection - mQuickScrubSection;
-            goToPageWithHaptic(pageToGoTo);
+            if (mFinishedTransitionToQuickScrub) {
+                goToPageWithHaptic(pageToGoTo);
+            }
             if (ENABLE_AUTO_ADVANCE) {
                 if (quickScrubSection == NUM_QUICK_SCRUB_SECTIONS || quickScrubSection == 0) {
                     mAutoAdvanceAlarm.setAlarm(mHasAlarmRun
@@ -116,36 +123,45 @@
     }
 
     public void onQuickSwitch() {
-        for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
-            TaskView taskView = (TaskView) mRecentsView.getPageAt(i);
-            if (taskView.getTask().key.id != mRecentsView.getRunningTaskId()) {
-                Runnable launchTaskRunnable = () -> taskView.launchTask(true);
-                if (mRecentsView.snapToPage(i, QUICK_SWITCH_SNAP_DURATION)) {
-                    // Snap to the new page then launch it
-                    mRecentsView.setNextPageSwitchRunnable(launchTaskRunnable);
-                } else {
-                    // No need to move page, just launch task directly
-                    launchTaskRunnable.run();
-                }
-                break;
-            }
-        }
-        mLauncher.getUserEventDispatcher().logActionOnControl(Touch.FLING,
-                ControlType.QUICK_SCRUB_BUTTON, null, mStartPage == 0 ?
-                        ContainerType.WORKSPACE : ContainerType.APP);
+        mQuickSwitched = true;
+        quickSwitchIfReady();
     }
 
-    public void snapToPageForCurrentQuickScrubSection() {
-        if (mInQuickScrub) {
-            goToPageWithHaptic(mRecentsView.getFirstTaskIndex() + mQuickScrubSection);
+    public void onFinishedTransitionToQuickScrub() {
+        mFinishedTransitionToQuickScrub = true;
+        quickSwitchIfReady();
+    }
+
+    /**
+     * Immediately launches the current task (which we snapped to in onQuickScrubStart) if we've
+     * gotten the onQuickSwitch callback and the transition to quick scrub has completed.
+     */
+    private void quickSwitchIfReady() {
+        if (mQuickSwitched && mFinishedTransitionToQuickScrub) {
+            onQuickScrubEnd();
+            mActivity.getUserEventDispatcher().logActionOnControl(Touch.FLING,
+                    ControlType.QUICK_SCRUB_BUTTON, null, mStartedFromHome ?
+                            ContainerType.WORKSPACE : ContainerType.APP);
+        }
+    }
+
+    public void snapToNextTaskIfAvailable() {
+        if (mInQuickScrub && mRecentsView.getChildCount() > 0) {
+            int toPage = mStartedFromHome ? 0 : mRecentsView.getNextPage() + 1;
+            goToPageWithHaptic(toPage, QUICK_SWITCH_START_DURATION);
         }
     }
 
     private void goToPageWithHaptic(int pageToGoTo) {
-        pageToGoTo = Utilities.boundToRange(pageToGoTo, mStartPage, mRecentsView.getPageCount() - 1);
+        goToPageWithHaptic(pageToGoTo, -1);
+    }
+
+    private void goToPageWithHaptic(int pageToGoTo, int overrideDuration) {
+        pageToGoTo = Utilities.boundToRange(pageToGoTo, 0, mRecentsView.getPageCount() - 1);
         if (pageToGoTo != mRecentsView.getNextPage()) {
-            int duration = Math.abs(pageToGoTo - mRecentsView.getNextPage())
-                    * QUICKSCRUB_SNAP_DURATION_PER_PAGE;
+            int duration = overrideDuration > -1 ? overrideDuration
+                    : Math.abs(pageToGoTo - mRecentsView.getNextPage())
+                            * QUICKSCRUB_SNAP_DURATION_PER_PAGE;
             mRecentsView.snapToPage(pageToGoTo, duration);
             mRecentsView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP,
                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -158,7 +174,7 @@
         if (mQuickScrubSection == NUM_QUICK_SCRUB_SECTIONS
                 && currPage < mRecentsView.getPageCount() - 1) {
             goToPageWithHaptic(currPage + 1);
-        } else if (mQuickScrubSection == 0 && currPage > mStartPage) {
+        } else if (mQuickScrubSection == 0 && currPage > 0) {
             goToPageWithHaptic(currPage - 1);
         }
         mHasAlarmRun = true;
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index f92d773..12e1a2b 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -15,34 +15,82 @@
  */
 package com.android.quickstep;
 
-import android.app.ListActivity;
+import android.app.ActivityOptions;
 import android.os.Bundle;
-import android.os.UserHandle;
-import android.support.annotation.Nullable;
-import android.widget.ArrayAdapter;
+import android.view.View;
 
-import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
-import com.android.systemui.shared.recents.model.RecentsTaskLoader;
-import com.android.systemui.shared.recents.model.Task;
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+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;
 
 /**
  * A simple activity to show the recently launched tasks
  */
-public class RecentsActivity extends ListActivity {
+public class RecentsActivity extends BaseDraggingActivity {
 
-    private ArrayAdapter<Task> mAdapter;
+    private RecentsRootView mRecentsRootView;
+    private FallbackRecentsView mFallbackRecentsView;
 
     @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
+    protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(this);
-        plan.preloadPlan(new PreloadOptions(), new RecentsTaskLoader(this, 1, 1, 0), -1,
-                UserHandle.myUserId());
+        // In case we are reusing IDP, create a copy so that we dont conflict with Launcher
+        // activity.
+        LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+        setDeviceProfile(appState != null
+                ? appState.getInvariantDeviceProfile().getDeviceProfile(this).copy(this)
+                : new InvariantDeviceProfile(this).getDeviceProfile(this));
 
-        mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
-        mAdapter.addAll(plan.getTaskStack().getTasks());
-        setListAdapter(mAdapter);
+        setContentView(R.layout.fallback_recents_activity);
+        mRecentsRootView = findViewById(R.id.drag_layer);
+        mFallbackRecentsView = findViewById(R.id.overview_panel);
+
+        RecentsActivityTracker.onRecentsActivityCreate(this);
+    }
+
+    @Override
+    public BaseDragLayer getDragLayer() {
+        return mRecentsRootView;
+    }
+
+    @Override
+    public View getRootView() {
+        return mRecentsRootView;
+    }
+
+    @Override
+    public <T extends View> T getOverviewPanel() {
+        return (T) mFallbackRecentsView;
+    }
+
+    @Override
+    public BadgeInfo getBadgeInfoForItem(ItemInfo info) {
+        return null;
+    }
+
+    @Override
+    public ActivityOptions getActivityLaunchOptions(View v, boolean useDefaultLaunchOptions) {
+        return null;
+    }
+
+    @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/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 3e3b3b2..392b73f 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -31,7 +31,6 @@
 import android.util.LruCache;
 import android.util.SparseArray;
 
-import com.android.launcher3.Launcher;
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.util.Preconditions;
@@ -211,13 +210,13 @@
 
     public void onStart() {
         mRecentsTaskLoader.startLoader(mContext);
-//        mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(true);
+        mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(true);
     }
 
     public void onTrimMemory(int level) {
         if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
             // We already stop the loader in UI_HIDDEN, so stop the high res loader as well
-//            mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(false);
+            mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(false);
         }
         mRecentsTaskLoader.onTrimMemory(level);
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsRootView.java b/quickstep/src/com/android/quickstep/RecentsRootView.java
new file mode 100644
index 0000000..24785f9
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsRootView.java
@@ -0,0 +1,58 @@
+/*
+ * 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.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
+
+public class RecentsRootView extends BaseDragLayer<RecentsActivity> {
+
+    private final BaseActivity mActivity;
+
+    public RecentsRootView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mActivity = BaseActivity.fromContext(context);
+        mControllers = new TouchController[0];
+    }
+
+    @TargetApi(23)
+    @Override
+    protected boolean fitSystemWindows(Rect insets) {
+        // Update device profile before notifying the children.
+        mActivity.getDeviceProfile().updateInsets(insets);
+        setInsets(insets);
+        return true; // I'll take it from here
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        // If the insets haven't changed, this is a no-op. Avoid unnecessary layout caused by
+        // modifying child layout params.
+        if (!insets.equals(mInsets)) {
+            super.setInsets(insets);
+        }
+        setBackground(insets.top == 0 ? null
+                : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
+    }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
deleted file mode 100644
index 9ae41eb..0000000
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ /dev/null
@@ -1,664 +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.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.quickstep.TaskView.CURVE_FACTOR;
-import static com.android.quickstep.TaskView.CURVE_INTERPOLATOR;
-
-import android.animation.LayoutTransition;
-import android.animation.LayoutTransition.TransitionListener;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.Shader;
-import android.graphics.Shader.TileMode;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.SparseBooleanArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.PagedView;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.uioverrides.OverviewState;
-import com.android.launcher3.uioverrides.RecentsViewStateController;
-import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.shared.recents.model.RecentsTaskLoader;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.TaskStack;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-
-import java.util.ArrayList;
-
-/**
- * A list of recent tasks.
- */
-public class RecentsView extends PagedView implements Insettable, OnSharedPreferenceChangeListener {
-
-    private static final Rect sTempStableInsets = new Rect();
-
-    public static final int SCROLL_TYPE_NONE = 0;
-    public static final int SCROLL_TYPE_TASK = 1;
-    public static final int SCROLL_TYPE_WORKSPACE = 2;
-
-    private static final String PREF_FLIP_RECENTS = "pref_flip_recents";
-
-    private final Launcher mLauncher;
-    private QuickScrubController mQuickScrubController;
-    private final ScrollState mScrollState = new ScrollState();
-    private boolean mOverviewStateEnabled;
-    private boolean mTaskStackListenerRegistered;
-    private LayoutTransition mLayoutTransition;
-    private Runnable mNextPageSwitchRunnable;
-
-    private float mFastFlingVelocity;
-
-    /**
-     * TODO: Call reloadIdNeeded in onTaskStackChanged.
-     */
-    private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
-        @Override
-        public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
-            for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
-                final TaskView taskView = (TaskView) getChildAt(i);
-                if (taskView.getTask().key.id == taskId) {
-                    taskView.getThumbnail().setThumbnail(taskView.getTask(), snapshot);
-                    return;
-                }
-            }
-        }
-    };
-
-    private RecentsViewStateController mStateController;
-    private int mFirstTaskIndex;
-
-    private final RecentsModel mModel;
-    private int mLoadPlanId = -1;
-
-    // Only valid until the launcher state changes to NORMAL
-    private int mRunningTaskId = -1;
-
-    private Bitmap mScrim;
-    private Paint mFadePaint;
-    private Shader mFadeShader;
-    private Matrix mFadeMatrix;
-    private boolean mScrimOnLeft;
-
-    private boolean mFirstTaskIconScaledDown = false;
-    private SparseBooleanArray mPrevVisibleTasks = new SparseBooleanArray();
-
-    public RecentsView(Context context) {
-        this(context, null);
-    }
-
-    public RecentsView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
-        enableFreeScroll(true);
-        setupLayoutTransition();
-        setClipToOutline(true);
-
-        mLauncher = Launcher.getLauncher(context);
-        mQuickScrubController = new QuickScrubController(mLauncher, this);
-        mModel = RecentsModel.getInstance(context);
-
-        onSharedPreferenceChanged(Utilities.getPrefs(context), PREF_FLIP_RECENTS);
-    }
-
-    @Override
-    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
-        if (s.equals(PREF_FLIP_RECENTS)) {
-            mIsRtl = Utilities.isRtl(getResources());
-            if (sharedPreferences.getBoolean(PREF_FLIP_RECENTS, false)) {
-                mIsRtl = !mIsRtl;
-            }
-            setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
-            mScrollState.isRtl = mIsRtl;
-        }
-    }
-
-    public boolean isRtl() {
-        return mIsRtl;
-    }
-
-    public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
-        for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
-            final TaskView taskView = (TaskView) getChildAt(i);
-            if (taskView.getTask().key.id == taskId) {
-                taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
-                taskView.setAlpha(1);
-                return taskView;
-            }
-        }
-        return null;
-    }
-
-    private void setupLayoutTransition() {
-        // We want to show layout transitions when pages are deleted, to close the gap.
-        // TODO: We should this manually so we can control the animation (fill in the gap as the
-        // dismissing task is being tracked, and also so we can update the visible task data during
-        // the transition. For now, the workaround is to expand the visible tasks to load.
-        mLayoutTransition = new LayoutTransition();
-        mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
-        mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
-
-        mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
-        mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
-        mLayoutTransition.addTransitionListener(new TransitionListener() {
-            @Override
-            public void startTransition(LayoutTransition layoutTransition, ViewGroup viewGroup,
-                    View view, int i) {
-                loadVisibleTaskData();
-            }
-
-            @Override
-            public void endTransition(LayoutTransition layoutTransition, ViewGroup viewGroup,
-                    View view, int i) {
-                loadVisibleTaskData();
-            }
-        });
-        setLayoutTransition(mLayoutTransition);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        Resources res = getResources();
-        mFirstTaskIndex = getPageCount();
-        mFastFlingVelocity = res.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
-    }
-
-    @Override
-    protected void onWindowVisibilityChanged(int visibility) {
-        super.onWindowVisibilityChanged(visibility);
-        updateTaskStackListenerState();
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        updateTaskStackListenerState();
-        Utilities.getPrefs(getContext()).registerOnSharedPreferenceChangeListener(this);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        updateTaskStackListenerState();
-        Utilities.getPrefs(getContext()).unregisterOnSharedPreferenceChangeListener(this);
-    }
-
-    @Override
-    public void setInsets(Rect insets) {
-        mInsets.set(insets);
-        DeviceProfile dp = Launcher.getLauncher(getContext()).getDeviceProfile();
-        Rect padding = getPadding(dp, getContext());
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
-        lp.bottomMargin = padding.bottom;
-        setLayoutParams(lp);
-
-        setPadding(padding.left, padding.top, padding.right, 0);
-
-        if (dp.isVerticalBarLayout()) {
-            boolean wasScrimOnLeft = mScrimOnLeft;
-            mScrimOnLeft = dp.isSeascape();
-
-            if (mScrim == null || wasScrimOnLeft != mScrimOnLeft) {
-                Drawable scrim = getContext().getDrawable(mScrimOnLeft
-                        ? R.drawable.recents_horizontal_scrim_left
-                        : R.drawable.recents_horizontal_scrim_right);
-                if (scrim instanceof BitmapDrawable) {
-                    mScrim = ((BitmapDrawable) scrim).getBitmap();
-                    mFadePaint = new Paint();
-                    mFadePaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
-                    mFadeShader = new BitmapShader(mScrim, TileMode.CLAMP, TileMode.REPEAT);
-                    mFadeMatrix = new Matrix();
-                } else {
-                    mScrim = null;
-                }
-            }
-        } else {
-            mScrim = null;
-            mFadePaint = null;
-            mFadeShader = null;
-            mFadeMatrix = null;
-        }
-    }
-
-    public int getFirstTaskIndex() {
-        return mFirstTaskIndex;
-    }
-
-    public boolean isTaskViewVisible(TaskView tv) {
-        // For now, just check if it's the active task or an adjacent task
-        return Math.abs(indexOfChild(tv) - getNextPage()) <= 1;
-    }
-
-    public TaskView getTaskView(int taskId) {
-        for (int i = getFirstTaskIndex(); i < getChildCount(); i++) {
-            TaskView tv = (TaskView) getChildAt(i);
-            if (tv.getTask().key.id == taskId) {
-                return tv;
-            }
-        }
-        return null;
-    }
-
-    public void setStateController(RecentsViewStateController stateController) {
-        mStateController = stateController;
-    }
-
-    public RecentsViewStateController getStateController() {
-        return mStateController;
-    }
-
-    public void setOverviewStateEnabled(boolean enabled) {
-        mOverviewStateEnabled = enabled;
-        updateTaskStackListenerState();
-    }
-
-    public void setNextPageSwitchRunnable(Runnable r) {
-        mNextPageSwitchRunnable = r;
-    }
-
-    @Override
-    protected void onPageEndTransition() {
-        super.onPageEndTransition();
-        if (mNextPageSwitchRunnable != null) {
-            mNextPageSwitchRunnable.run();
-            mNextPageSwitchRunnable = null;
-        }
-    }
-
-    private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
-        final RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
-        TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
-        if (stack == null) {
-            removeAllViews();
-            return;
-        }
-
-        int oldChildCount = getChildCount();
-
-        // Ensure there are as many views as there are tasks in the stack (adding and trimming as
-        // necessary)
-        final LayoutInflater inflater = LayoutInflater.from(getContext());
-        final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
-        setLayoutTransition(null);
-
-        final int requiredChildCount = tasks.size() + mFirstTaskIndex;
-        for (int i = getChildCount(); i < requiredChildCount; i++) {
-            final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
-            addView(taskView);
-        }
-        while (getChildCount() > requiredChildCount) {
-            final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
-            final Task task = taskView.getTask();
-            removeView(taskView);
-            loader.unloadTaskData(task);
-//            loader.getHighResThumbnailLoader().onTaskInvisible(task);
-        }
-        setLayoutTransition(mLayoutTransition);
-
-        // Rebind and reset all task views
-        for (int i = tasks.size() - 1; i >= 0; i--) {
-            final int pageIndex = tasks.size() - i - 1 + mFirstTaskIndex;
-            final Task task = tasks.get(i);
-            final TaskView taskView = (TaskView) getChildAt(pageIndex);
-            taskView.bind(task);
-            taskView.resetVisualProperties();
-        }
-        updateCurveProperties();
-        // Reload the set of visible task's data
-        mPrevVisibleTasks.clear();
-        loadVisibleTaskData();
-        applyIconScale(false /* animate */);
-
-        if (oldChildCount != getChildCount()) {
-            mQuickScrubController.snapToPageForCurrentQuickScrubSection();
-        }
-    }
-
-    private void updateTaskStackListenerState() {
-        boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
-                && getWindowVisibility() == VISIBLE;
-        if (registerStackListener != mTaskStackListenerRegistered) {
-            if (registerStackListener) {
-                ActivityManagerWrapper.getInstance()
-                        .registerTaskStackListener(mTaskStackListener);
-                reloadIfNeeded();
-            } else {
-                ActivityManagerWrapper.getInstance()
-                        .unregisterTaskStackListener(mTaskStackListener);
-            }
-            mTaskStackListenerRegistered = registerStackListener;
-        }
-    }
-
-    private static Rect getPadding(DeviceProfile profile, Context context) {
-        WindowManagerWrapper.getInstance().getStableInsets(sTempStableInsets);
-        Rect padding = new Rect(profile.workspacePadding);
-
-        float taskWidth = profile.widthPx - sTempStableInsets.left - sTempStableInsets.right;
-        float taskHeight = profile.heightPx - sTempStableInsets.top - sTempStableInsets.bottom;
-
-        float overviewHeight, overviewWidth;
-        if (profile.isVerticalBarLayout()) {
-            float scrimLength = context.getResources()
-                    .getDimension(R.dimen.recents_page_fade_length);
-            float maxPadding = Math.max(padding.left, padding.right);
-
-            // Use the same padding on both sides for symmetry.
-            float availableWidth = taskWidth - 2 * Math.max(maxPadding, scrimLength);
-            float availableHeight = profile.availableHeightPx - padding.top - padding.bottom
-                    - sTempStableInsets.top;
-            float scaledRatio = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
-            overviewHeight = taskHeight * scaledRatio;
-            overviewWidth = taskWidth * scaledRatio;
-
-        } else {
-            overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
-                    - sTempStableInsets.top;
-            overviewWidth = taskWidth * overviewHeight / taskHeight;
-        }
-
-        padding.bottom = profile.availableHeightPx - padding.top - sTempStableInsets.top
-                - Math.round(overviewHeight);
-        padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
-        return padding;
-    }
-
-    /**
-     * Sets the {@param outRect} to match the position of the first tile such that it is scaled
-     * down to match the 2nd taskView.
-     * @return returns the factor which determines the scaling factor for the second task.
-     */
-    public static float getScaledDownPageRect(DeviceProfile dp, Context context, Rect outRect) {
-        getPageRect(dp, context, outRect);
-
-        int pageSpacing = context.getResources()
-                .getDimensionPixelSize(R.dimen.recents_page_spacing);
-        float halfScreenWidth = dp.widthPx * 0.5f;
-        float halfPageWidth = outRect.width() * 0.5f;
-        float pageCenter = outRect.right + pageSpacing + halfPageWidth;
-        float distanceFromCenter = Math.abs(halfScreenWidth - pageCenter);
-        float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
-        float linearInterpolation = Math.min(1, distanceFromCenter / distanceToReachEdge);
-
-        float scale = 1 - CURVE_INTERPOLATOR.getInterpolation(linearInterpolation) * CURVE_FACTOR;
-
-        int topMargin = context.getResources()
-                .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
-        outRect.top -= topMargin;
-        Utilities.scaleRectAboutCenter(outRect, scale);
-        outRect.top += (int) (scale * topMargin);
-        return linearInterpolation;
-    }
-
-    public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
-        Rect targetPadding = getPadding(grid, context);
-        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);
-        outRect.top += context.getResources()
-                .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
-    }
-
-    @Override
-    protected boolean computeScrollHelper() {
-        boolean scrolling = super.computeScrollHelper();
-        boolean isFlingingFast = false;
-        updateCurveProperties();
-        if (scrolling || (mTouchState == TOUCH_STATE_SCROLLING)) {
-            if (scrolling) {
-                // Check if we are flinging quickly to disable high res thumbnail loading
-                isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity;
-            }
-
-            // After scrolling, update the visible task's data
-            loadVisibleTaskData();
-        }
-
-        // Update the high res thumbnail loader
-        RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
-//        loader.getHighResThumbnailLoader().setFlingingFast(isFlingingFast);
-        return scrolling;
-    }
-
-    /**
-     * Scales and adjusts translation of adjacent pages as if on a curved carousel.
-     */
-    public void updateCurveProperties() {
-        if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
-            return;
-        }
-        final int halfPageWidth = mScrollState.halfPageWidth = getNormalChildWidth() / 2;
-        mScrollState.lastScrollType = SCROLL_TYPE_NONE;
-        final int screenCenter = mInsets.left + getPaddingLeft() + getScrollX() + halfPageWidth;
-        final int halfScreenWidth = getMeasuredWidth() / 2;
-        final int pageSpacing = mPageSpacing;
-
-        final int pageCount = getPageCount();
-        for (int i = 0; i < pageCount; i++) {
-            View page = getPageAt(i);
-            int pageCenter = page.getLeft() + halfPageWidth;
-            mScrollState.distanceFromScreenCenter = screenCenter - pageCenter;
-            float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
-            mScrollState.linearInterpolation = Math.min(1,
-                    Math.abs(mScrollState.distanceFromScreenCenter) / distanceToReachEdge);
-            mScrollState.lastScrollType = ((PageCallbacks) page).onPageScroll(mScrollState);
-        }
-    }
-
-    /**
-     * Iterates through all thet asks, and loads the associated task data for newly visible tasks,
-     * and unloads the associated task data for tasks that are no longer visible.
-     */
-    private void loadVisibleTaskData() {
-        RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
-        int centerPageIndex = getPageNearestToCenterOfScreen();
-        int lower = Math.max(mFirstTaskIndex, centerPageIndex - 2);
-        int upper = Math.min(centerPageIndex + 2, getChildCount() - 1);
-        for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
-            TaskView taskView = (TaskView) getChildAt(i);
-            Task task = taskView.getTask();
-            boolean visible = lower <= i && i <= upper;
-            if (visible) {
-                if (!mPrevVisibleTasks.get(i)) {
-                    loader.loadTaskData(task);
-//                    loader.getHighResThumbnailLoader().onTaskVisible(task);
-                }
-            } else {
-                if (mPrevVisibleTasks.get(i)) {
-                    loader.unloadTaskData(task);
-//                    loader.getHighResThumbnailLoader().onTaskInvisible(task);
-                }
-            }
-            mPrevVisibleTasks.put(i, visible);
-        }
-    }
-
-    public void onTaskDismissed(TaskView taskView) {
-        ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
-        removeView(taskView);
-        if (getTaskCount() == 0) {
-            mLauncher.getStateManager().goToState(NORMAL);
-        }
-    }
-
-    public void reset() {
-        mRunningTaskId = -1;
-        setCurrentPage(0);
-    }
-
-    public int getTaskCount() {
-        return getChildCount() - mFirstTaskIndex;
-    }
-
-    public int getRunningTaskId() {
-        return mRunningTaskId;
-    }
-
-    /**
-     * Reloads the view if anything in recents changed.
-     */
-    public void reloadIfNeeded() {
-        if (!mModel.isLoadPlanValid(mLoadPlanId)) {
-            mLoadPlanId = mModel.loadTasks(mRunningTaskId, this::applyLoadPlan);
-        }
-    }
-
-    /**
-     * Ensures that the first task in the view represents {@param task} and reloads the view
-     * if needed. This allows the swipe-up gesture to assume that the first tile always
-     * corresponds to the correct task.
-     * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
-     * is called.
-     * Also scrolls the view to this task
-     */
-    public void showTask(int runningTaskId) {
-        boolean needsReload = false;
-        if (getTaskCount() == 0) {
-            needsReload = true;
-            // Add an empty view for now
-            setLayoutTransition(null);
-            final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
-                    .inflate(R.layout.task, this, false);
-            addView(taskView, mFirstTaskIndex);
-            setLayoutTransition(mLayoutTransition);
-        }
-        mRunningTaskId = runningTaskId;
-        setCurrentPage(mFirstTaskIndex);
-        if (!needsReload) {
-            needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
-        }
-        if (needsReload) {
-            mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
-        } else {
-            loadVisibleTaskData();
-        }
-        if (mCurrentPage >= mFirstTaskIndex) {
-            getPageAt(mCurrentPage).setAlpha(0);
-        }
-    }
-
-    public QuickScrubController getQuickScrubController() {
-        return mQuickScrubController;
-    }
-
-    public void setFirstTaskIconScaledDown(boolean isScaledDown, boolean animate) {
-        if (mFirstTaskIconScaledDown == isScaledDown) {
-            return;
-        }
-        mFirstTaskIconScaledDown = isScaledDown;
-        applyIconScale(animate);
-    }
-
-    private void applyIconScale(boolean animate) {
-        float scale = mFirstTaskIconScaledDown ? 0 : 1;
-        TaskView firstTask = (TaskView) getChildAt(mFirstTaskIndex);
-        if (firstTask != null) {
-            if (animate) {
-                firstTask.animateIconToScale(scale);
-            } else {
-                firstTask.setIconScale(scale);
-            }
-        }
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        if (mScrim == null) {
-            super.draw(canvas);
-            return;
-        }
-
-        final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
-
-        int length = mScrim.getWidth();
-        int height = getHeight();
-        int saveCount = canvas.getSaveCount();
-
-        int scrimLeft;
-        if (mScrimOnLeft) {
-            scrimLeft = getScrollX();
-        } else {
-            scrimLeft = getScrollX() + getWidth() - length;
-        }
-        canvas.saveLayer(scrimLeft, 0, scrimLeft + length, height, null, flags);
-        super.draw(canvas);
-
-        mFadeMatrix.setTranslate(scrimLeft, 0);
-        mFadeShader.setLocalMatrix(mFadeMatrix);
-        mFadePaint.setShader(mFadeShader);
-        canvas.drawRect(scrimLeft, 0, scrimLeft + length, height, mFadePaint);
-        canvas.restoreToCount(saveCount);
-    }
-
-    public interface PageCallbacks {
-
-        /**
-         * Updates the page UI based on scroll params and returns the type of scroll
-         * effect performed.
-         *
-         * @see #SCROLL_TYPE_NONE
-         * @see #SCROLL_TYPE_TASK
-         * @see #SCROLL_TYPE_WORKSPACE
-         */
-        int onPageScroll(ScrollState scrollState);
-    }
-
-    public static class ScrollState {
-
-        public boolean isRtl;
-        public int lastScrollType;
-
-        public int halfPageWidth;
-        public float distanceFromScreenCenter;
-        public float linearInterpolation;
-
-        public float prevPageExtraWidth;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/SimpleTaskView.java b/quickstep/src/com/android/quickstep/SimpleTaskView.java
deleted file mode 100644
index 8425fa3..0000000
--- a/quickstep/src/com/android/quickstep/SimpleTaskView.java
+++ /dev/null
@@ -1,52 +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.content.Context;
-import android.graphics.Point;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.WindowManager;
-
-/**
- * A simple view which keeps its size proportional to the display size
- */
-public class SimpleTaskView extends View {
-
-    private static final Point sTempPoint = new Point();
-
-    public SimpleTaskView(Context context) {
-        super(context);
-    }
-
-    public SimpleTaskView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public SimpleTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int height = MeasureSpec.getSize(heightMeasureSpec);
-        getContext().getSystemService(WindowManager.class)
-                .getDefaultDisplay().getRealSize(sTempPoint);
-
-        int width = (int) ((float) height * sTempPoint.x / sTempPoint.y);
-        setMeasuredDimension(width, height);
-    }
-}
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/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
index fb14fb8..08be0c8 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -16,7 +16,8 @@
 
 package com.android.quickstep;
 
-import android.app.ActivityOptions;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+
 import android.content.ComponentName;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -31,12 +32,17 @@
 import android.view.ViewTreeObserver.OnPreDrawListener;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.util.InstantAppResolver;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskThumbnailView;
+import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
@@ -56,6 +62,7 @@
 public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut {
 
     private static final String TAG = "TaskSystemShortcut";
+    private static final int DISMISS_TASK_DURATION = 300;
 
     protected T mSystemShortcut;
 
@@ -69,11 +76,12 @@
     }
 
     @Override
-    public View.OnClickListener getOnClickListener(Launcher launcher, ItemInfo itemInfo) {
+    public View.OnClickListener getOnClickListener(
+            BaseDraggingActivity activity, ItemInfo itemInfo) {
         return null;
     }
 
-    public View.OnClickListener getOnClickListener(final Launcher launcher, final TaskView view) {
+    public View.OnClickListener getOnClickListener(BaseDraggingActivity activity, TaskView view) {
         Task task = view.getTask();
 
         ShortcutInfo dummyInfo = new ShortcutInfo();
@@ -81,14 +89,14 @@
         ComponentName component = task.getTopComponent();
         dummyInfo.intent.setComponent(component);
         dummyInfo.user = UserHandle.of(task.key.userId);
-        dummyInfo.title = TaskUtils.getTitle(launcher, task);
+        dummyInfo.title = TaskUtils.getTitle(activity, task);
 
-        return getOnClickListenerForTask(launcher, task, dummyInfo);
+        return getOnClickListenerForTask(activity, task, dummyInfo);
     }
 
-    protected View.OnClickListener getOnClickListenerForTask(final Launcher launcher,
-            final Task task, final ItemInfo dummyInfo) {
-        return mSystemShortcut.getOnClickListener(launcher, dummyInfo);
+    protected View.OnClickListener getOnClickListenerForTask(
+            BaseDraggingActivity activity, Task task, ItemInfo dummyInfo) {
+        return mSystemShortcut.getOnClickListener(activity, dummyInfo);
     }
 
     public static class AppInfo extends TaskSystemShortcut<SystemShortcut.AppInfo> {
@@ -97,10 +105,13 @@
         }
     }
 
-    public static class SplitScreen extends TaskSystemShortcut implements OnPreDrawListener {
+    public static class SplitScreen extends TaskSystemShortcut implements OnPreDrawListener,
+            DeviceProfile.OnDeviceProfileChangeListener, View.OnLayoutChangeListener {
 
         private Handler mHandler;
+        private RecentsView mRecentsView;
         private TaskView mTaskView;
+        private BaseDraggingActivity mActivity;
 
         public SplitScreen() {
             super(R.drawable.ic_split_screen, R.string.recent_task_option_split_screen);
@@ -108,22 +119,27 @@
         }
 
         @Override
-        public View.OnClickListener getOnClickListener(Launcher launcher, TaskView taskView) {
-            if (launcher.getDeviceProfile().isMultiWindowMode) {
+        public View.OnClickListener getOnClickListener(
+                BaseDraggingActivity activity, TaskView taskView) {
+            if (activity.getDeviceProfile().isMultiWindowMode) {
                 return null;
             }
             final Task task  = taskView.getTask();
+            final int taskId = task.key.id;
             if (!task.isDockable) {
                 return null;
             }
+            mActivity = activity;
+            mRecentsView = activity.getOverviewPanel();
             mTaskView = taskView;
+            final TaskThumbnailView thumbnailView = taskView.getThumbnail();
             return (v -> {
-                AbstractFloatingView.closeOpenViews(launcher, true,
+                AbstractFloatingView.closeOpenViews(activity, true,
                         AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
 
-                if (ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key.id,
+                if (ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
                         ActivityOptionsCompat.makeSplitScreenOptions(true))) {
-                    ISystemUiProxy sysUiProxy = RecentsModel.getInstance(launcher).getSystemUiProxy();
+                    ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy();
                     try {
                         sysUiProxy.onSplitScreenInvoked();
                     } catch (RemoteException e) {
@@ -131,26 +147,35 @@
                         return;
                     }
 
+                    // Add a device profile change listener to kick off animating the side tasks
+                    // once we enter multiwindow mode and relayout
+                    activity.addOnDeviceProfileChangeListener(this);
+
                     final Runnable animStartedListener = () -> {
+                        // Hide the task view and wait for the window to be resized
+                        // TODO: Consider animating in launcher and do an in-place start activity
+                        //       afterwards
+                        mRecentsView.addIgnoreResetTask(mTaskView);
+                        mTaskView.setAlpha(0f);
                         mTaskView.getViewTreeObserver().addOnPreDrawListener(SplitScreen.this);
-                        launcher.<RecentsView>getOverviewPanel().removeView(taskView);
                     };
 
                     final int[] position = new int[2];
-                    taskView.getLocationOnScreen(position);
-                    final int width = (int) (taskView.getWidth() * taskView.getScaleX());
-                    final int height = (int) (taskView.getHeight() * taskView.getScaleY());
+                    thumbnailView.getLocationOnScreen(position);
+                    final int width = (int) (thumbnailView.getWidth() * taskView.getScaleX());
+                    final int height = (int) (thumbnailView.getHeight() * taskView.getScaleY());
                     final Rect taskBounds = new Rect(position[0], position[1],
                             position[0] + width, position[1] + height);
 
                     Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
-                            taskBounds.width(), taskBounds.height(), taskView, 1f, Color.BLACK);
+                            taskBounds.width(), taskBounds.height(), thumbnailView, 1f,
+                            Color.BLACK);
                     AppTransitionAnimationSpecsFuture future =
                             new AppTransitionAnimationSpecsFuture(mHandler) {
                         @Override
                         public List<AppTransitionAnimationSpecCompat> composeSpecs() {
                             return Collections.singletonList(new AppTransitionAnimationSpecCompat(
-                                    task.key.id, thumbnail, taskBounds));
+                                    taskId, thumbnail, taskBounds));
                         }
                     };
                     WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
@@ -165,6 +190,31 @@
             WindowManagerWrapper.getInstance().endProlongedAnimations();
             return true;
         }
+
+        @Override
+        public void onDeviceProfileChanged(DeviceProfile dp) {
+            mActivity.removeOnDeviceProfileChangeListener(this);
+            if (dp.isMultiWindowMode) {
+                mTaskView.getRootView().addOnLayoutChangeListener(this);
+            }
+        }
+
+        @Override
+        public void onLayoutChange(View v, int l, int t, int r, int b,
+                int oldL, int oldT, int oldR, int oldB) {
+            mTaskView.getRootView().removeOnLayoutChangeListener(this);
+            mRecentsView.removeIgnoreResetTask(mTaskView);
+
+            // Start animating in the side pages once launcher has been resized
+            PendingAnimation pendingAnim = mRecentsView.createTaskDismissAnimation(mTaskView,
+                    false, false, DISMISS_TASK_DURATION);
+            AnimatorPlaybackController controller = AnimatorPlaybackController.wrap(
+                    pendingAnim.anim, DISMISS_TASK_DURATION);
+            controller.dispatchOnStart();
+            controller.setEndAction(() -> pendingAnim.finish(true));
+            controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN);
+            controller.start();
+        }
     }
 
     public static class Pin extends TaskSystemShortcut {
@@ -177,8 +227,9 @@
         }
 
         @Override
-        public View.OnClickListener getOnClickListener(Launcher launcher, TaskView taskView) {
-            ISystemUiProxy sysUiProxy = RecentsModel.getInstance(launcher).getSystemUiProxy();
+        public View.OnClickListener getOnClickListener(
+                BaseDraggingActivity activity, TaskView taskView) {
+            ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy();
             if (sysUiProxy == null) {
                 return null;
             }
@@ -210,11 +261,11 @@
         }
 
         @Override
-        protected View.OnClickListener getOnClickListenerForTask(Launcher launcher, Task task,
-                ItemInfo itemInfo) {
-            if (InstantAppResolver.newInstance(launcher).isInstantApp(launcher,
+        protected View.OnClickListener getOnClickListenerForTask(
+                BaseDraggingActivity activity, Task task, ItemInfo itemInfo) {
+            if (InstantAppResolver.newInstance(activity).isInstantApp(activity,
                         task.getTopComponent().getPackageName())) {
-                return mSystemShortcut.createOnClickListener(launcher, itemInfo);
+                return mSystemShortcut.createOnClickListener(activity, itemInfo);
             }
             return null;
         }
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index b31d42f..2df951b 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -16,12 +16,12 @@
 
 package com.android.quickstep;
 
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.os.UserHandle;
 import android.util.Log;
 
-import com.android.launcher3.Launcher;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.systemui.shared.recents.model.Task;
@@ -34,10 +34,10 @@
 
     private static final String TAG = "TaskUtils";
 
-    public static CharSequence getTitle(Launcher launcher, Task task) {
-        LauncherAppsCompat launcherAppsCompat = LauncherAppsCompat.getInstance(launcher);
-        UserManagerCompat userManagerCompat = UserManagerCompat.getInstance(launcher);
-        PackageManager packageManager = launcher.getPackageManager();
+    public static CharSequence getTitle(Context context, Task task) {
+        LauncherAppsCompat launcherAppsCompat = LauncherAppsCompat.getInstance(context);
+        UserManagerCompat userManagerCompat = UserManagerCompat.getInstance(context);
+        PackageManager packageManager = context.getPackageManager();
         UserHandle user = UserHandle.of(task.key.userId);
         ApplicationInfo applicationInfo = launcherAppsCompat.getApplicationInfo(
             task.getTopComponent().getPackageName(), 0, user);
diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java
index f35f6a6..768fbda 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -21,8 +21,6 @@
 import android.view.Choreographer;
 import android.view.MotionEvent;
 
-import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.function.Consumer;
@@ -64,4 +62,14 @@
     default Choreographer getIntrimChoreographer(MotionEventQueue queue) {
         return null;
     }
+
+    default void deferInit() { }
+
+    default boolean deferNextEventToMainThread() {
+        return false;
+    }
+
+    default boolean forceToLauncherConsumer() {
+        return false;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index e5af3e5..df7214e 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -21,17 +21,13 @@
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
-
 import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_START_DURATION;
+import static com.android.launcher3.LauncherState.NORMAL;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.Service;
-import android.content.ComponentName;
 import android.content.Intent;
-import android.content.pm.ResolveInfo;
 import android.graphics.PointF;
 import android.os.Build;
 import android.os.Handler;
@@ -42,15 +38,17 @@
 import android.util.SparseArray;
 import android.view.Choreographer;
 import android.view.MotionEvent;
+import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
 
 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.uioverrides.UiFactory;
 import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -62,6 +60,8 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class TouchInteractionService extends Service {
 
+    public static final boolean DEBUG_OPEN_OVERVIEW_VIA_ALT_TAB = false;
+
     private static final SparseArray<String> sMotionEventNames;
 
     static {
@@ -105,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
@@ -132,6 +130,30 @@
             mEventQueue.onQuickScrubEnd();
             TraceHelper.endSection("SysUiBinder", "onQuickScrubEnd");
         }
+
+        @Override
+        public void onOverviewToggle() {
+            mOverviewCommandHelper.onOverviewToggle();
+        }
+
+        @Override
+        public void onOverviewShown(boolean triggeredFromAltTab) {
+            if (DEBUG_OPEN_OVERVIEW_VIA_ALT_TAB) {
+                mOverviewCommandHelper.onOverviewShown();
+            }
+        }
+
+        @Override
+        public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+            if (DEBUG_OPEN_OVERVIEW_VIA_ALT_TAB) {
+                mOverviewCommandHelper.onOverviewHidden();
+            }
+        }
+
+        @Override
+        public void onQuickStep(MotionEvent motionEvent) throws RemoteException {
+
+        }
     };
 
     private final TouchConsumer mNoOpTouchConsumer = (ev) -> {};
@@ -143,17 +165,15 @@
     }
 
     private ActivityManagerWrapper mAM;
-    private RunningTaskInfo mRunningTask;
     private RecentsModel mRecentsModel;
-    private Intent mHomeIntent;
-    private ComponentName mLauncher;
     private MotionEventQueue mEventQueue;
     private MainThreadExecutor mMainThreadExecutor;
     private ISystemUiProxy mISystemUiProxy;
+    private OverviewCommandHelper mOverviewCommandHelper;
+    private OverviewInteractionState mOverviewInteractionState;
 
     private Choreographer mMainThreadChoreographer;
     private Choreographer mBackgroundThreadChoreographer;
-    private MotionEventQueue mNoOpEventQueue;
 
     @Override
     public void onCreate() {
@@ -161,19 +181,10 @@
         mAM = ActivityManagerWrapper.getInstance();
         mRecentsModel = RecentsModel.getInstance(this);
         mMainThreadExecutor = new MainThreadExecutor();
-
-        mHomeIntent = new Intent(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_HOME)
-                .setPackage(getPackageName())
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        ResolveInfo info = getPackageManager().resolveActivity(mHomeIntent, 0);
-        mLauncher = new ComponentName(getPackageName(), info.activityInfo.name);
-        // Clear the packageName as system can fail to dedupe it b/64108432
-        mHomeIntent.setComponent(mLauncher).setPackage(null);
-
+        mOverviewCommandHelper = new OverviewCommandHelper(this);
         mMainThreadChoreographer = Choreographer.getInstance();
-        mNoOpEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
-        mEventQueue = mNoOpEventQueue;
+        mEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
+        mOverviewInteractionState = OverviewInteractionState.getInstance(this);
 
         sConnected = true;
 
@@ -195,31 +206,46 @@
     }
 
     private void onBinderPreMotionEvent(@HitTarget int downHitTarget) {
-        mRunningTask = mAM.getRunningTask();
-
         mEventQueue.reset();
-
-        if (mRunningTask == null) {
-            mEventQueue = mNoOpEventQueue;
-        } else if (mRunningTask.topActivity.equals(mLauncher)) {
-            mEventQueue = getLauncherEventQueue();
-        } else {
+        TouchConsumer oldConsumer = mEventQueue.getConsumer();
+        if (oldConsumer.deferNextEventToMainThread()) {
             mEventQueue = new MotionEventQueue(mMainThreadChoreographer,
-                    new OtherActivityTouchConsumer(this, mRunningTask, mRecentsModel,
-                    mHomeIntent, mISystemUiProxy, mMainThreadExecutor,
-                    mBackgroundThreadChoreographer, downHitTarget));
+                    new DeferredTouchConsumer((v) -> getCurrentTouchConsumer(downHitTarget,
+                            oldConsumer.forceToLauncherConsumer(), v)));
+            mEventQueue.deferInit();
+        } else {
+            mEventQueue = new MotionEventQueue(
+                    mMainThreadChoreographer, getCurrentTouchConsumer(downHitTarget, false, null));
         }
     }
 
-    private MotionEventQueue getLauncherEventQueue() {
+    private TouchConsumer getCurrentTouchConsumer(
+            @HitTarget int downHitTarget, boolean forceToLauncher, VelocityTracker tracker) {
+        RunningTaskInfo runningTaskInfo = mAM.getRunningTask();
+
+        if (runningTaskInfo == null && !forceToLauncher) {
+            return mNoOpTouchConsumer;
+        } else if (forceToLauncher ||
+                runningTaskInfo.topActivity.equals(mOverviewCommandHelper.launcher)) {
+            return getLauncherConsumer();
+        } else {
+            if (tracker == null) {
+                tracker = VelocityTracker.obtain();
+            }
+            return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel,
+                            mOverviewCommandHelper.homeIntent,
+                            mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor,
+                            mBackgroundThreadChoreographer, downHitTarget, tracker);
+        }
+    }
+
+    private TouchConsumer getLauncherConsumer() {
         Launcher launcher = (Launcher) LauncherAppState.getInstance(this).getModel().getCallback();
         if (launcher == null) {
-            return mNoOpEventQueue;
+            return mNoOpTouchConsumer;
         }
-
         View target = launcher.getDragLayer();
-        return new MotionEventQueue(mMainThreadChoreographer,
-                new LauncherTouchConsumer(launcher, target));
+        return new LauncherTouchConsumer(launcher, target);
     }
 
     private static class LauncherTouchConsumer implements TouchConsumer {
@@ -304,13 +330,12 @@
             if (TouchConsumer.isInteractionQuick(interactionType)) {
                 Runnable action = () -> {
                     Runnable onComplete = null;
-                    if (interactionType == INTERACTION_QUICK_SCRUB) {
-                        mQuickScrubController.onQuickScrubStart(true);
-                    } else if (interactionType == INTERACTION_QUICK_SWITCH) {
+                    if (interactionType == INTERACTION_QUICK_SWITCH) {
                         onComplete = mQuickScrubController::onQuickSwitch;
                     }
-                    mLauncher.getStateManager().goToState(FAST_OVERVIEW, true, 0,
-                            QUICK_SWITCH_START_DURATION, onComplete);
+                    LauncherState fromState = mLauncher.getStateManager().getState();
+                    mLauncher.getStateManager().goToState(FAST_OVERVIEW, true, onComplete);
+                    mQuickScrubController.onQuickScrubStart(fromState == NORMAL);
                 };
 
                 if (mLauncher.getWorkspace().runOnOverlayHidden(action)) {
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index ede0ad2..2d2a483 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;
@@ -41,7 +35,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.metrics.LogMaker;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
@@ -53,14 +46,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,11 +60,16 @@
 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.util.SysuiEventLogger;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.recents.utilities.RectFEvaluator;
 import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.TransactionCompat;
@@ -81,42 +77,8 @@
 
 import java.util.StringJoiner;
 
-class EventLogTags {
-    private EventLogTags() {
-    }  // don't instantiate
-
-    /** 524292 sysui_multi_action (content|4) */
-    public static final int SYSUI_MULTI_ACTION = 524292;
-
-    public static void writeSysuiMultiAction(Object[] content) {
-        android.util.EventLog.writeEvent(SYSUI_MULTI_ACTION, content);
-    }
-}
-
-class MetricsLogger {
-    private static MetricsLogger sMetricsLogger;
-
-    private static MetricsLogger getLogger() {
-        if (sMetricsLogger == null) {
-            sMetricsLogger = new MetricsLogger();
-        }
-        return sMetricsLogger;
-    }
-
-    protected void saveLog(Object[] rep) {
-        EventLogTags.writeSysuiMultiAction(rep);
-    }
-
-    public void write(LogMaker content) {
-        if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
-            content.setType(4/*MetricsEvent.TYPE_ACTION*/);
-        }
-        saveLog(content.serialize());
-    }
-}
-
 @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;
 
@@ -190,6 +152,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;
 
@@ -203,12 +167,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;
 
@@ -229,12 +195,16 @@
     private Matrix mTmpMatrix = new Matrix();
     private final long mTouchTimeMs;
     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);
@@ -307,7 +277,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,
@@ -328,14 +299,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() {
@@ -350,40 +313,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) {
@@ -391,31 +353,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() {
 
@@ -424,21 +376,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) {
@@ -446,7 +395,7 @@
     }
 
     private void launcherFrameDrawn() {
-        View rootView = mLauncher.getRootView();
+        View rootView = mActivity.getRootView();
         if (rootView.getAlpha() < 1) {
             if (mGestureStarted) {
                 final MultiStateCallback callback = mStateCallback;
@@ -465,18 +414,15 @@
     }
 
     private void initializeLauncherAnimationController() {
-        mLauncherLayoutListener.setHandler(this);
+        mLayoutListener.setHandler(this);
         onLauncherLayoutChanged();
 
-        // Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
-        // "Recents" activity for app transition tests for the app-to-recents case.
-        final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
-        builder.setPackageName("com.android.systemui");
-        builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
-                "com.android.systemui.recents.RecentsActivity");
-        builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
-                mLauncherFrameDrawnTime - mTouchTimeMs);
-        mMetricsLogger.write(builder);
+        final long transitionDelay = mLauncherFrameDrawnTime - mTouchTimeMs;
+        SysuiEventLogger.writeDummyRecentsTransition(transitionDelay);
+
+        if (LatencyTrackerCompat.isEnabled(mContext)) {
+            LatencyTrackerCompat.logToggleRecents((int) transitionDelay);
+        }
     }
 
     public void updateInteractionType(@InteractionType int interactionType) {
@@ -498,7 +444,7 @@
     }
 
     private void onQuickInteractionStart() {
-        mLauncher.getStateManager().goToState(FAST_OVERVIEW,
+        mActivityControlHelper.onQuickInteractionStart(mActivity,
                 mWasLauncherAlreadyVisible || mGestureStarted);
         mQuickScrubController.onQuickScrubStart(false);
     }
@@ -513,32 +459,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);
         }
     }
@@ -588,9 +516,8 @@
                 mLauncherTransitionController.setPlayFraction(shift);
 
                 // Make sure the window follows the first task if it moves, e.g. during quick scrub.
-                int firstTaskIndex = mRecentsView.getFirstTaskIndex();
-                View firstTask = mRecentsView.getPageAt(firstTaskIndex);
-                int scrollForFirstTask = mRecentsView.getScrollForPage(firstTaskIndex);
+                View firstTask = mRecentsView.getPageAt(0);
+                int scrollForFirstTask = mRecentsView.getScrollForPage(0);
                 int offsetFromFirstTask = (scrollForFirstTask - mRecentsView.getScrollX());
                 if (offsetFromFirstTask != 0) {
                     synchronized (mTargetRect) {
@@ -600,6 +527,12 @@
                         mTargetRect.offset(offsetX, 0);
                     }
                 }
+                if (mRecentsAnimationWrapper.controller != null) {
+
+                    // TODO: This logic is spartanic!
+                    mRecentsAnimationWrapper.controller.setAnimationTargetsBehindSystemBars(
+                            shift < 0.12f);
+                }
             };
             if (Looper.getMainLooper() == Looper.myLooper()) {
                 runOnUi.run();
@@ -646,14 +579,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);
     }
 
@@ -669,9 +601,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);
         }
     }
 
@@ -721,13 +654,14 @@
 
     /** 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) {
-                setStateOnUiThread((Float.compare(mCurrentShift.value, 0) == 0)
-                        ? STATE_SCALED_CONTROLLER_APP : STATE_SCALED_CONTROLLER_RECENTS);
+                setStateOnUiThread(mIsGoingToHome ?
+                        STATE_SCALED_CONTROLLER_RECENTS : STATE_SCALED_CONTROLLER_APP);
             }
         });
         anim.start();
@@ -748,31 +682,26 @@
     }
 
     private void invalidateHandler() {
-        mCurrentShift.cancelAnimation();
+        mCurrentShift.finishAnimation();
 
         if (mGestureEndCallback != null) {
             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() {
@@ -801,17 +730,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;
                         }
                     }
@@ -827,8 +747,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 */);
@@ -882,4 +801,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/util/SysuiEventLogger.java b/quickstep/src/com/android/quickstep/util/SysuiEventLogger.java
new file mode 100644
index 0000000..d474ded
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SysuiEventLogger.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.metrics.LogMaker;
+import android.util.EventLog;
+
+/**
+ * Utility class for writing logs on behalf of systemUI
+ */
+public class SysuiEventLogger {
+
+    /** 524292 sysui_multi_action (content|4) */
+    public static final int SYSUI_MULTI_ACTION = 524292;
+
+    private static void write(LogMaker content) {
+        if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
+            content.setType(4/*MetricsEvent.TYPE_ACTION*/);
+        }
+        EventLog.writeEvent(SYSUI_MULTI_ACTION, content.serialize());
+    }
+
+    public static void writeDummyRecentsTransition(long transitionDelay) {
+        // Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
+        // "Recents" activity for app transition tests for the app-to-recents case.
+        final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
+        builder.setPackageName("com.android.systemui");
+        builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
+                "com.android.systemui.recents.RecentsActivity");
+        builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
+                transitionDelay);
+        write(builder);
+    }
+}
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/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
new file mode 100644
index 0000000..7989e84
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.Shader.TileMode;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+
+/**
+ * {@link RecentsView} used in Launcher activity
+ */
+public class LauncherRecentsView extends RecentsView<Launcher> implements Insettable {
+
+    private Bitmap mScrim;
+    private Paint mFadePaint;
+    private Shader mFadeShader;
+    private Matrix mFadeMatrix;
+    private boolean mScrimOnLeft;
+
+    public LauncherRecentsView(Context context) {
+        this(context, null);
+    }
+
+    public LauncherRecentsView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+        DeviceProfile dp = mActivity.getDeviceProfile();
+        Rect padding = getPadding(dp, getContext());
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+        lp.bottomMargin = padding.bottom;
+        setLayoutParams(lp);
+
+        setPadding(padding.left, padding.top, padding.right, 0);
+
+        if (dp.isVerticalBarLayout()) {
+            boolean wasScrimOnLeft = mScrimOnLeft;
+            mScrimOnLeft = dp.isSeascape();
+
+            if (mScrim == null || wasScrimOnLeft != mScrimOnLeft) {
+                Drawable scrim = getContext().getDrawable(mScrimOnLeft
+                        ? R.drawable.recents_horizontal_scrim_left
+                        : R.drawable.recents_horizontal_scrim_right);
+                if (scrim instanceof BitmapDrawable) {
+                    mScrim = ((BitmapDrawable) scrim).getBitmap();
+                    mFadePaint = new Paint();
+                    mFadePaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
+                    mFadeShader = new BitmapShader(mScrim, TileMode.CLAMP, TileMode.REPEAT);
+                    mFadeMatrix = new Matrix();
+                } else {
+                    mScrim = null;
+                }
+            }
+        } else {
+            mScrim = null;
+            mFadePaint = null;
+            mFadeShader = null;
+            mFadeMatrix = null;
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mScrim == null) {
+            super.draw(canvas);
+            return;
+        }
+
+        final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
+
+        int length = mScrim.getWidth();
+        int height = getHeight();
+        int saveCount = canvas.getSaveCount();
+
+        int scrimLeft;
+        if (mScrimOnLeft) {
+            scrimLeft = getScrollX();
+        } else {
+            scrimLeft = getScrollX() + getWidth() - length;
+        }
+        canvas.saveLayer(scrimLeft, 0, scrimLeft + length, height, null, flags);
+        super.draw(canvas);
+
+        mFadeMatrix.setTranslate(scrimLeft, 0);
+        mFadeShader.setLocalMatrix(mFadeMatrix);
+        mFadePaint.setShader(mFadeShader);
+        canvas.drawRect(scrimLeft, 0, scrimLeft + length, height, mFadePaint);
+        canvas.restoreToCount(saveCount);
+    }
+
+    @Override
+    protected void onAllTasksRemoved() {
+        mActivity.getStateManager().goToState(NORMAL);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
new file mode 100644
index 0000000..9c0e580
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -0,0 +1,633 @@
+/*
+ * 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.views;
+
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+import android.util.SparseBooleanArray;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.quickstep.PendingAnimation;
+import com.android.quickstep.QuickScrubController;
+import com.android.quickstep.RecentsModel;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+import java.util.ArrayList;
+
+/**
+ * A list of recent tasks.
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public abstract class RecentsView<T extends BaseActivity>
+        extends PagedView implements OnSharedPreferenceChangeListener {
+
+    private static final String PREF_FLIP_RECENTS = "pref_flip_recents";
+
+    private static final Rect sTempStableInsets = new Rect();
+
+    protected final T mActivity;
+    private final QuickScrubController mQuickScrubController;
+    private final float mFastFlingVelocity;
+    private final RecentsModel mModel;
+
+    private final ScrollState mScrollState = new ScrollState();
+    // Keeps track of the previously known visible tasks for purposes of loading/unloading task data
+    private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
+
+    /**
+     * TODO: Call reloadIdNeeded in onTaskStackChanged.
+     */
+    private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+        @Override
+        public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
+            for (int i = 0; i < getChildCount(); i++) {
+                final TaskView taskView = (TaskView) getChildAt(i);
+                if (taskView.getTask().key.id == taskId) {
+                    taskView.getThumbnail().setThumbnail(taskView.getTask(), snapshot);
+                    return;
+                }
+            }
+        }
+    };
+
+    private int mLoadPlanId = -1;
+
+    // Only valid until the launcher state changes to NORMAL
+    private int mRunningTaskId = -1;
+
+    private boolean mFirstTaskIconScaledDown = false;
+
+    private boolean mOverviewStateEnabled;
+    private boolean mTaskStackListenerRegistered;
+    private Runnable mNextPageSwitchRunnable;
+
+    private PendingAnimation mPendingAnimation;
+
+    // Keeps track of task views whose visual state should not be reset
+    private ArraySet<TaskView> mIgnoreResetTaskViews = new ArraySet<>();
+
+    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
+        enableFreeScroll(true);
+        setClipToOutline(true);
+
+        mFastFlingVelocity = getResources()
+                .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
+        mActivity = (T) BaseActivity.fromContext(context);
+        mQuickScrubController = new QuickScrubController(mActivity, this);
+        mModel = RecentsModel.getInstance(context);
+
+        onSharedPreferenceChanged(Utilities.getPrefs(context), PREF_FLIP_RECENTS);
+    }
+
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+        if (s.equals(PREF_FLIP_RECENTS)) {
+            mIsRtl = Utilities.isRtl(getResources());
+            if (sharedPreferences.getBoolean(PREF_FLIP_RECENTS, false)) {
+                mIsRtl = !mIsRtl;
+            }
+            setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
+        }
+    }
+
+    public boolean isRtl() {
+        return mIsRtl;
+    }
+
+    public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
+        for (int i = 0; i < getChildCount(); i++) {
+            final TaskView taskView = (TaskView) getChildAt(i);
+            if (taskView.getTask().key.id == taskId) {
+                taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
+                taskView.setAlpha(1);
+                return taskView;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        updateTaskStackListenerState();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        updateTaskStackListenerState();
+        Utilities.getPrefs(getContext()).registerOnSharedPreferenceChangeListener(this);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        updateTaskStackListenerState();
+        Utilities.getPrefs(getContext()).unregisterOnSharedPreferenceChangeListener(this);
+    }
+
+    @Override
+    public void onViewRemoved(View child) {
+        super.onViewRemoved(child);
+
+        // Clear the task data for the removed child if it was visible
+        Task task = ((TaskView) child).getTask();
+        if (mHasVisibleTaskData.get(task.key.id)) {
+            mHasVisibleTaskData.delete(task.key.id);
+            RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+            loader.unloadTaskData(task);
+            loader.getHighResThumbnailLoader().onTaskInvisible(task);
+        }
+    }
+
+    public boolean isTaskViewVisible(TaskView tv) {
+        // For now, just check if it's the active task or an adjacent task
+        return Math.abs(indexOfChild(tv) - getNextPage()) <= 1;
+    }
+
+    public TaskView getTaskView(int taskId) {
+        for (int i = 0; i < getChildCount(); i++) {
+            TaskView tv = (TaskView) getChildAt(i);
+            if (tv.getTask().key.id == taskId) {
+                return tv;
+            }
+        }
+        return null;
+    }
+
+    public void setOverviewStateEnabled(boolean enabled) {
+        mOverviewStateEnabled = enabled;
+        updateTaskStackListenerState();
+    }
+
+    public void setNextPageSwitchRunnable(Runnable r) {
+        mNextPageSwitchRunnable = r;
+    }
+
+    @Override
+    protected void onPageEndTransition() {
+        super.onPageEndTransition();
+        if (mNextPageSwitchRunnable != null) {
+            mNextPageSwitchRunnable.run();
+            mNextPageSwitchRunnable = null;
+        }
+    }
+
+    private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
+        if (mPendingAnimation != null) {
+            mPendingAnimation.addEndListener((b) -> applyLoadPlan(loadPlan));
+            return;
+        }
+        TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
+        if (stack == null) {
+            removeAllViews();
+            return;
+        }
+
+        int oldChildCount = getChildCount();
+
+        // Ensure there are as many views as there are tasks in the stack (adding and trimming as
+        // necessary)
+        final LayoutInflater inflater = LayoutInflater.from(getContext());
+        final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
+
+        final int requiredChildCount = tasks.size();
+        for (int i = getChildCount(); i < requiredChildCount; i++) {
+            final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
+            addView(taskView);
+        }
+        while (getChildCount() > requiredChildCount) {
+            final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
+            removeView(taskView);
+        }
+
+        // Unload existing visible task data
+        unloadVisibleTaskData();
+
+        // Rebind and reset all task views
+        for (int i = requiredChildCount - 1; i >= 0; i--) {
+            final int pageIndex = requiredChildCount - i - 1;
+            final Task task = tasks.get(i);
+            final TaskView taskView = (TaskView) getChildAt(pageIndex);
+            taskView.bind(task);
+        }
+        resetTaskVisuals();
+        applyIconScale(false /* animate */);
+
+        if (oldChildCount != getChildCount()) {
+            mQuickScrubController.snapToNextTaskIfAvailable();
+        }
+    }
+
+    public void resetTaskVisuals() {
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            TaskView taskView = (TaskView) getChildAt(i);
+            if (!mIgnoreResetTaskViews.contains(taskView)) {
+                taskView.resetVisualProperties();
+            }
+        }
+
+        updateCurveProperties();
+        // Update the set of visible task's data
+        loadVisibleTaskData();
+    }
+
+    private void updateTaskStackListenerState() {
+        boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
+                && getWindowVisibility() == VISIBLE;
+        if (registerStackListener != mTaskStackListenerRegistered) {
+            if (registerStackListener) {
+                ActivityManagerWrapper.getInstance()
+                        .registerTaskStackListener(mTaskStackListener);
+                reloadIfNeeded();
+            } else {
+                ActivityManagerWrapper.getInstance()
+                        .unregisterTaskStackListener(mTaskStackListener);
+            }
+            mTaskStackListenerRegistered = registerStackListener;
+        }
+    }
+
+    protected static Rect getPadding(DeviceProfile profile, Context context) {
+        WindowManagerWrapper.getInstance().getStableInsets(sTempStableInsets);
+        Rect padding = new Rect(profile.workspacePadding);
+
+        float taskWidth = profile.widthPx - sTempStableInsets.left - sTempStableInsets.right;
+        float taskHeight = profile.heightPx - sTempStableInsets.top - sTempStableInsets.bottom;
+
+        float overviewHeight, overviewWidth;
+        if (profile.isVerticalBarLayout()) {
+            float scrimLength = context.getResources()
+                    .getDimension(R.dimen.recents_page_fade_length);
+            float maxPadding = Math.max(padding.left, padding.right);
+
+            // Use the same padding on both sides for symmetry.
+            float availableWidth = taskWidth - 2 * Math.max(maxPadding, scrimLength);
+            float availableHeight = profile.availableHeightPx - padding.top - padding.bottom
+                    - sTempStableInsets.top;
+            float scaledRatio = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
+            overviewHeight = taskHeight * scaledRatio;
+            overviewWidth = taskWidth * scaledRatio;
+
+        } else {
+            overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
+                    - sTempStableInsets.top;
+            overviewWidth = taskWidth * overviewHeight / taskHeight;
+        }
+
+        padding.bottom = profile.availableHeightPx - padding.top - sTempStableInsets.top
+                - Math.round(overviewHeight);
+        padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
+        return padding;
+    }
+
+    public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
+        Rect targetPadding = getPadding(grid, context);
+        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);
+        outRect.top += context.getResources()
+                .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+    }
+
+    @Override
+    protected boolean computeScrollHelper() {
+        boolean scrolling = super.computeScrollHelper();
+        boolean isFlingingFast = false;
+        updateCurveProperties();
+        if (scrolling || (mTouchState == TOUCH_STATE_SCROLLING)) {
+            if (scrolling) {
+                // Check if we are flinging quickly to disable high res thumbnail loading
+                isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity;
+            }
+
+            // After scrolling, update the visible task's data
+            loadVisibleTaskData();
+        }
+
+        // Update the high res thumbnail loader
+        RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+        loader.getHighResThumbnailLoader().setFlingingFast(isFlingingFast);
+        return scrolling;
+    }
+
+    /**
+     * Scales and adjusts translation of adjacent pages as if on a curved carousel.
+     */
+    public void updateCurveProperties() {
+        if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
+            return;
+        }
+        final int halfPageWidth = getNormalChildWidth() / 2;
+        final int screenCenter = mInsets.left + getPaddingLeft() + getScrollX() + halfPageWidth;
+        final int halfScreenWidth = getMeasuredWidth() / 2;
+        final int pageSpacing = mPageSpacing;
+
+        final int pageCount = getPageCount();
+        for (int i = 0; i < pageCount; i++) {
+            View page = getPageAt(i);
+            float pageCenter = page.getLeft() + page.getTranslationX() + halfPageWidth;
+            float distanceFromScreenCenter = screenCenter - pageCenter;
+            float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
+            mScrollState.linearInterpolation = Math.min(1,
+                    Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
+            ((PageCallbacks) page).onPageScroll(mScrollState);
+        }
+    }
+
+    /**
+     * Iterates through all thet asks, and loads the associated task data for newly visible tasks,
+     * and unloads the associated task data for tasks that are no longer visible.
+     */
+    public void loadVisibleTaskData() {
+        RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+        int centerPageIndex = getPageNearestToCenterOfScreen();
+        int lower = Math.max(0, centerPageIndex - 2);
+        int upper = Math.min(centerPageIndex + 2, getChildCount() - 1);
+        int numChildren = getChildCount();
+
+        // Update the task data for the in/visible children
+        for (int i = 0; i < numChildren; i++) {
+            TaskView taskView = (TaskView) getChildAt(i);
+            Task task = taskView.getTask();
+            boolean visible = lower <= i && i <= upper;
+            if (visible) {
+                if (!mHasVisibleTaskData.get(task.key.id)) {
+                    loader.loadTaskData(task);
+                    loader.getHighResThumbnailLoader().onTaskVisible(task);
+                }
+                mHasVisibleTaskData.put(task.key.id, visible);
+            } else {
+                if (mHasVisibleTaskData.get(task.key.id)) {
+                    loader.unloadTaskData(task);
+                    loader.getHighResThumbnailLoader().onTaskInvisible(task);
+                }
+                mHasVisibleTaskData.delete(task.key.id);
+            }
+        }
+    }
+
+    /**
+     * Unloads any associated data from the currently visible tasks
+     */
+    private void unloadVisibleTaskData() {
+        RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+        for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
+            if (mHasVisibleTaskData.valueAt(i)) {
+                TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
+                Task task = taskView.getTask();
+                loader.unloadTaskData(task);
+                loader.getHighResThumbnailLoader().onTaskInvisible(task);
+            }
+        }
+        mHasVisibleTaskData.clear();
+    }
+
+
+    protected abstract void onAllTasksRemoved();
+
+    public void reset() {
+        unloadVisibleTaskData();
+        mRunningTaskId = -1;
+        setCurrentPage(0);
+    }
+
+    /**
+     * Reloads the view if anything in recents changed.
+     */
+    public void reloadIfNeeded() {
+        if (!mModel.isLoadPlanValid(mLoadPlanId)) {
+            mLoadPlanId = mModel.loadTasks(mRunningTaskId, this::applyLoadPlan);
+        }
+    }
+
+    /**
+     * Ensures that the first task in the view represents {@param task} and reloads the view
+     * if needed. This allows the swipe-up gesture to assume that the first tile always
+     * corresponds to the correct task.
+     * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
+     * is called.
+     * Also scrolls the view to this task
+     */
+    public void showTask(int runningTaskId) {
+        boolean needsReload = false;
+        if (getChildCount() == 0) {
+            needsReload = true;
+            // Add an empty view for now
+            final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
+                    .inflate(R.layout.task, this, false);
+            addView(taskView, 0);
+        }
+        mRunningTaskId = runningTaskId;
+        setCurrentPage(0);
+        if (!needsReload) {
+            needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
+        }
+        if (needsReload) {
+            mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
+        } else {
+            loadVisibleTaskData();
+        }
+        getPageAt(mCurrentPage).setAlpha(0);
+    }
+
+    public QuickScrubController getQuickScrubController() {
+        return mQuickScrubController;
+    }
+
+    public void setFirstTaskIconScaledDown(boolean isScaledDown, boolean animate) {
+        if (mFirstTaskIconScaledDown == isScaledDown) {
+            return;
+        }
+        mFirstTaskIconScaledDown = isScaledDown;
+        applyIconScale(animate);
+    }
+
+    private void applyIconScale(boolean animate) {
+        float scale = mFirstTaskIconScaledDown ? 0 : 1;
+        TaskView firstTask = (TaskView) getChildAt(0);
+        if (firstTask != null) {
+            if (animate) {
+                firstTask.animateIconToScale(scale);
+            } else {
+                firstTask.setIconScale(scale);
+            }
+        }
+    }
+
+    public interface PageCallbacks {
+
+        /**
+         * Updates the page UI based on scroll params.
+         */
+        default void onPageScroll(ScrollState scrollState) {};
+    }
+
+    public static class ScrollState {
+
+        /**
+         * The progress from 0 to 1, where 0 is the center
+         * of the screen and 1 is the edge of the screen.
+         */
+        public float linearInterpolation;
+    }
+
+    public void addIgnoreResetTask(TaskView taskView) {
+        mIgnoreResetTaskViews.add(taskView);
+    }
+
+    public void removeIgnoreResetTask(TaskView taskView) {
+        mIgnoreResetTaskViews.remove(taskView);
+    }
+
+    public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
+            boolean removeTask, long duration) {
+        if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
+            throw new IllegalStateException("Another pending animation is still running");
+        }
+        AnimatorSet anim = new AnimatorSet();
+        PendingAnimation pendingAnimation = new PendingAnimation(anim);
+
+        int count = getChildCount();
+        if (count == 0) {
+            return pendingAnimation;
+        }
+
+        int[] oldScroll = new int[count];
+        getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
+
+        int[] newScroll = new int[count];
+        getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView);
+
+        int maxScrollDiff = 0;
+        int lastPage = mIsRtl ? 0 : count - 1;
+        if (getChildAt(lastPage) == taskView) {
+            if (count > 1) {
+                int secondLastPage = mIsRtl ? 1 : count - 2;
+                maxScrollDiff = oldScroll[lastPage] - newScroll[secondLastPage];
+            }
+        }
+
+        boolean needsCurveUpdates = false;
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            if (child == taskView) {
+                if (animateTaskView) {
+                    addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
+                    addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
+                            duration, LINEAR, anim);
+                }
+            } else {
+                int scrollDiff = newScroll[i] - oldScroll[i] + maxScrollDiff;
+                if (scrollDiff != 0) {
+                    addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff),
+                            duration, ACCEL, anim);
+                    needsCurveUpdates = true;
+                }
+            }
+        }
+
+        if (needsCurveUpdates) {
+            ValueAnimator va = ValueAnimator.ofFloat(0, 1);
+            va.addUpdateListener((a) -> updateCurveProperties());
+            anim.play(va);
+        }
+
+        // Add a tiny bit of translation Z, so that it draws on top of other views
+        if (animateTaskView) {
+            taskView.setTranslationZ(0.1f);
+        }
+
+        mPendingAnimation = pendingAnimation;
+        mPendingAnimation.addEndListener((isSuccess) -> {
+           if (isSuccess) {
+               if (removeTask) {
+                   ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
+               }
+               removeView(taskView);
+               if (getChildCount() == 0) {
+                   onAllTasksRemoved();
+               }
+           }
+           resetTaskVisuals();
+           mPendingAnimation = null;
+        });
+        return pendingAnimation;
+    }
+
+    private static void addAnim(ObjectAnimator anim, long duration,
+            TimeInterpolator interpolator, AnimatorSet set) {
+        anim.setDuration(duration).setInterpolator(interpolator);
+        set.play(anim);
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (event.getKeyCode() == KeyEvent.KEYCODE_TAB
+                && event.getAction() == KeyEvent.ACTION_DOWN) {
+            snapToPage((getNextPage()
+                    + (event.isShiftPressed() ? getPageCount() - 1 : 1)) % getPageCount());
+            loadVisibleTaskData();
+            return true;
+        }
+        return super.dispatchKeyEvent(event);
+    }
+
+    public void snapToTaskAfterNext() {
+        snapToPage((getNextPage() + 1) % getPageCount());
+        loadVisibleTaskData();
+    }
+
+    public void launchNextTask() {
+        final TaskView nextTask = (TaskView) getChildAt(getNextPage());
+        nextTask.launchTask(true);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
similarity index 87%
rename from quickstep/src/com/android/quickstep/TaskMenuView.java
rename to quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 6bbcb37..30cbcdf 100644
--- a/quickstep/src/com/android/quickstep/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.quickstep;
+package com.android.quickstep.views;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -32,14 +32,16 @@
 import android.widget.TextView;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.shortcuts.DeepShortcutView;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.TaskSystemShortcut;
+import com.android.quickstep.TaskUtils;
 
 /**
  * Contains options for a recent task when long-pressing its icon.
@@ -58,7 +60,7 @@
 
     private static final long OPEN_CLOSE_DURATION = 220;
 
-    private Launcher mLauncher;
+    private BaseDraggingActivity mActivity;
     private TextView mTaskIconAndName;
     private AnimatorSet mOpenCloseAnimator;
     private TaskView mTaskView;
@@ -70,7 +72,7 @@
     public TaskMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
-        mLauncher = Launcher.getLauncher(context);
+        mActivity = BaseDraggingActivity.fromContext(context);
         setClipToOutline(true);
         setOutlineProvider(new ViewOutlineProvider() {
             @Override
@@ -90,7 +92,7 @@
     @Override
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            DragLayer dl = mLauncher.getDragLayer();
+            BaseDragLayer dl = mActivity.getDragLayer();
             if (!dl.isEventOverView(this, ev)) {
                 // TODO: log this once we have a new container type for it?
                 close(true);
@@ -120,9 +122,9 @@
     }
 
     public static boolean showForTask(TaskView taskView) {
-        Launcher launcher = Launcher.getLauncher(taskView.getContext());
-        final TaskMenuView taskMenuView = (TaskMenuView) launcher.getLayoutInflater().inflate(
-                        R.layout.task_menu, launcher.getDragLayer(), false);
+        BaseDraggingActivity activity = BaseDraggingActivity.fromContext(taskView.getContext());
+        final TaskMenuView taskMenuView = (TaskMenuView) activity.getLayoutInflater().inflate(
+                        R.layout.task_menu, activity.getDragLayer(), false);
         return taskMenuView.populateAndShowForTask(taskView);
     }
 
@@ -130,7 +132,7 @@
         if (isAttachedToWindow()) {
             return false;
         }
-        mLauncher.getDragLayer().addView(this);
+        mActivity.getDragLayer().addView(this);
         mTaskView = taskView;
         addMenuOptions(mTaskView);
         orientAroundTaskView(mTaskView);
@@ -143,11 +145,11 @@
         int iconSize = getResources().getDimensionPixelSize(R.dimen.task_thumbnail_icon_size);
         icon.setBounds(0, 0, iconSize, iconSize);
         mTaskIconAndName.setCompoundDrawables(null, icon, null, null);
-        mTaskIconAndName.setText(TaskUtils.getTitle(mLauncher, taskView.getTask()));
+        mTaskIconAndName.setText(TaskUtils.getTitle(getContext(), taskView.getTask()));
         mTaskIconAndName.setOnClickListener(v -> close(true));
 
         for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
-            OnClickListener onClickListener = menuOption.getOnClickListener(mLauncher, taskView);
+            OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, taskView);
             if (onClickListener != null) {
                 addMenuOption(menuOption, onClickListener);
             }
@@ -155,7 +157,7 @@
     }
 
     private void addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener) {
-        DeepShortcutView menuOptionView = (DeepShortcutView) mLauncher.getLayoutInflater().inflate(
+        DeepShortcutView menuOptionView = (DeepShortcutView) mActivity.getLayoutInflater().inflate(
                 R.layout.system_shortcut, this, false);
         menuOptionView.getIconView().setBackgroundResource(menuOption.iconResId);
         menuOptionView.getBubbleText().setText(menuOption.labelResId);
@@ -165,8 +167,8 @@
 
     private void orientAroundTaskView(TaskView taskView) {
         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-        mLauncher.getDragLayer().getDescendantRectRelativeToSelf(taskView, sTempRect);
-        Rect insets = mLauncher.getDragLayer().getInsets();
+        mActivity.getDragLayer().getDescendantRectRelativeToSelf(taskView, sTempRect);
+        Rect insets = mActivity.getDragLayer().getInsets();
         int x = sTempRect.left + (sTempRect.width() - getMeasuredWidth()) / 2 - insets.left;
         setX(Utilities.isRtl(getResources()) ? -x : x);
         setY(sTempRect.top - mTaskIconAndName.getPaddingTop() - insets.top);
@@ -209,7 +211,7 @@
 
     private void closeComplete() {
         mIsOpen = false;
-        mLauncher.getDragLayer().removeView(this);
+        mActivity.getDragLayer().removeView(this);
     }
 
     private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
similarity index 96%
rename from quickstep/src/com/android/quickstep/TaskThumbnailView.java
rename to quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 9b9c6f2..87bb53b 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.quickstep;
+package com.android.quickstep.views;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -33,9 +33,10 @@
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
+import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -74,6 +75,7 @@
         mCornerRadius = getResources().getDimension(R.dimen.task_corner_radius);
         mFadeLength = getResources().getDimension(R.dimen.task_fade_length);
         mOverlay = TaskOverlayFactory.get(context).createOverlay(this);
+        mPaint.setFilterBitmap(true);
     }
 
     public void bind() {
@@ -151,7 +153,8 @@
             } else {
                 final Configuration configuration =
                         getContext().getApplicationContext().getResources().getConfiguration();
-                final DeviceProfile profile = Launcher.getLauncher(getContext()).getDeviceProfile();
+                final DeviceProfile profile = BaseActivity.fromContext(getContext())
+                        .getDeviceProfile();
                 if (configuration.orientation == mThumbnailData.orientation) {
                     // If we are in the same orientation as the screenshot, just scale it to the
                     // width of the task view
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
similarity index 74%
rename from quickstep/src/com/android/quickstep/TaskView.java
rename to quickstep/src/com/android/quickstep/views/TaskView.java
index b407f75..c20b577 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -14,10 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.quickstep;
-
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
+package com.android.quickstep.views;
 
 import android.animation.TimeInterpolator;
 import android.app.ActivityOptions;
@@ -31,10 +28,11 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.quickstep.RecentsView.PageCallbacks;
-import com.android.quickstep.RecentsView.ScrollState;
+import com.android.quickstep.views.RecentsView.PageCallbacks;
+import com.android.quickstep.views.RecentsView.ScrollState;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -47,17 +45,15 @@
  */
 public class TaskView extends FrameLayout implements TaskCallbacks, PageCallbacks {
 
-    /** Designates how "curvy" the carousel is from 0 to 1, where 0 is a straight line. */
-    public static final float CURVE_FACTOR = 0.25f;
-    /** A circular curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
-    public static final TimeInterpolator CURVE_INTERPOLATOR
-            = x -> (float) (1 - Math.sqrt(1 - Math.pow(x, 2)));
+    /** A curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
+    private static final TimeInterpolator CURVE_INTERPOLATOR
+            = x -> (float) -Math.cos(x * Math.PI) / 2f + .5f;
 
     /**
      * The alpha of a black scrim on a page in the carousel as it leaves the screen.
      * In the resting position of the carousel, the adjacent pages have about half this scrim.
      */
-    private static final float MAX_PAGE_SCRIM_ALPHA = 0.8f;
+    private static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
 
     private static final long SCALE_ICON_DURATION = 120;
 
@@ -115,7 +111,8 @@
         if (mTask != null) {
             final ActivityOptions opts;
             if (animate) {
-                opts = Launcher.getLauncher(getContext()).getActivityLaunchOptions(this, false);
+                opts = BaseDraggingActivity.fromContext(getContext())
+                        .getActivityLaunchOptions(this, false);
             } else {
                 opts = ActivityOptions.makeCustomAnimation(getContext(), 0, 0);
             }
@@ -159,38 +156,16 @@
         setScaleY(1f);
         setTranslationX(0f);
         setTranslationY(0f);
+        setTranslationZ(0);
         setAlpha(1f);
     }
 
     @Override
-    public int onPageScroll(ScrollState scrollState) {
+    public void onPageScroll(ScrollState scrollState) {
         float curveInterpolation =
                 CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
-        float scale = 1 - curveInterpolation * CURVE_FACTOR;
-        setScaleX(scale);
-        setScaleY(scale);
-
-        // Make sure the biggest card (i.e. the one in front) shows on top of the adjacent ones.
-        setTranslationZ(scale);
 
         mSnapshotView.setDimAlpha(1 - curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
-
-        float translation =
-                scrollState.distanceFromScreenCenter * curveInterpolation * CURVE_FACTOR;
-
-        if (scrollState.lastScrollType == SCROLL_TYPE_WORKSPACE) {
-            // Make sure that the task cards do not overlap with the workspace card
-            float min = scrollState.halfPageWidth * (1 - scale);
-            if (scrollState.isRtl) {
-                setTranslationX(Math.min(translation, min) - scrollState.prevPageExtraWidth);
-            } else {
-                setTranslationX(Math.max(translation, -min) + scrollState.prevPageExtraWidth);
-            }
-        } else {
-            setTranslationX(translation);
-        }
-        scrollState.prevPageExtraWidth = 0;
-        return SCROLL_TYPE_TASK;
     }
 
     private static final class TaskOutlineProvider extends ViewOutlineProvider {
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index d07ff81..4693917 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -44,13 +44,6 @@
             layout="@layout/overview_panel"
             android:visibility="gone" />
 
-        <!-- DO NOT CHANGE THE ID -->
-        <include
-            android:id="@+id/hotseat"
-            layout="@layout/hotseat"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent" />
-
         <!-- Keep these behind the workspace so that they are not visible when
          we go into AllApps -->
         <com.android.launcher3.pageindicators.WorkspacePageIndicator
@@ -69,6 +62,13 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:visibility="invisible" />
+
+        <!-- DO NOT CHANGE THE ID -->
+        <include
+            android:id="@+id/hotseat"
+            layout="@layout/hotseat"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
     </com.android.launcher3.dragndrop.DragLayer>
 
 </com.android.launcher3.LauncherRootView>
diff --git a/res/layout/launcher_preference.xml b/res/layout/launcher_preference.xml
new file mode 100644
index 0000000..ed0ea7c
--- /dev/null
+++ b/res/layout/launcher_preference.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<com.android.launcher3.views.HighlightableListView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/list"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:cacheColorHint="@android:color/transparent"
+    android:clipToPadding="false"
+    android:drawSelectorOnTop="false"
+    android:orientation="vertical"
+    android:scrollbarAlwaysDrawVerticalTrack="true"
+    android:scrollbarStyle="outsideOverlay" />
\ No newline at end of file
diff --git a/quickstep/res/layout/longpress_options_menu.xml b/res/layout/longpress_options_menu.xml
similarity index 96%
rename from quickstep/res/layout/longpress_options_menu.xml
rename to res/layout/longpress_options_menu.xml
index 9cf0fcf..71d117a 100644
--- a/quickstep/res/layout/longpress_options_menu.xml
+++ b/res/layout/longpress_options_menu.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.launcher3.uioverrides.OptionsPopupView
+<com.android.launcher3.views.OptionsPopupView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
@@ -94,4 +94,4 @@
 
     </FrameLayout>
 
-</com.android.launcher3.uioverrides.OptionsPopupView>
\ No newline at end of file
+</com.android.launcher3.views.OptionsPopupView>
\ No newline at end of file
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index c795b81..bdd5d23 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -14,61 +14,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.launcher3.uioverrides.OverviewPanel
+<Space
       xmlns:android="http://schemas.android.com/apk/res/android"
-      android:theme="@style/HomeScreenElementTheme"
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:layout_gravity="center_horizontal|bottom"
-      android:gravity="top"
-      android:orientation="horizontal">
-
-    <TextView
-        android:id="@+id/wallpaper_button"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:drawablePadding="4dp"
-        android:drawableTop="@drawable/ic_wallpaper"
-        android:drawableTint="?attr/workspaceTextColor"
-        android:fontFamily="sans-serif-condensed"
-        android:gravity="center_horizontal"
-        android:stateListAnimator="@animator/overview_button_anim"
-        android:text="@string/wallpaper_button_text"
-        android:textAllCaps="true"
-        android:textColor="?attr/workspaceTextColor"
-        android:textSize="12sp" />
-
-    <TextView
-        android:id="@+id/widget_button"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:drawablePadding="4dp"
-        android:drawableTop="@drawable/ic_widget"
-        android:drawableTint="?attr/workspaceTextColor"
-        android:fontFamily="sans-serif-condensed"
-        android:gravity="center_horizontal"
-        android:stateListAnimator="@animator/overview_button_anim"
-        android:text="@string/widget_button_text"
-        android:textAllCaps="true"
-        android:textColor="?attr/workspaceTextColor"
-        android:textSize="12sp" />
-
-    <TextView
-        android:id="@+id/settings_button"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:drawablePadding="4dp"
-        android:drawableTop="@drawable/ic_setting"
-        android:drawableTint="?attr/workspaceTextColor"
-        android:fontFamily="sans-serif-condensed"
-        android:gravity="center_horizontal"
-        android:stateListAnimator="@animator/overview_button_anim"
-        android:text="@string/settings_button_text"
-        android:textAllCaps="true"
-        android:textColor="?attr/workspaceTextColor"
-        android:textSize="12sp" />
-
-</com.android.launcher3.uioverrides.OverviewPanel>
\ No newline at end of file
+      android:layout_width="0dp"
+      android:layout_height="0dp" />
\ No newline at end of file
diff --git a/res/layout/widgets_list_row_view.xml b/res/layout/widgets_list_row_view.xml
index 91baf7a..eec57a5 100644
--- a/res/layout/widgets_list_row_view.xml
+++ b/res/layout/widgets_list_row_view.xml
@@ -31,7 +31,6 @@
         android:layout_height="@dimen/widget_section_height"
         android:background="?android:attr/colorPrimary"
         android:drawablePadding="@dimen/widget_section_horizontal_padding"
-        android:ellipsize="end"
         android:focusable="true"
         android:gravity="start|center_vertical"
         android:paddingBottom="@dimen/widget_section_vertical_padding"
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/layout/work_tab_footer.xml b/res/layout/work_tab_footer.xml
index 21ff55e..379e9d0 100644
--- a/res/layout/work_tab_footer.xml
+++ b/res/layout/work_tab_footer.xml
@@ -17,6 +17,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:focusable="true"
     android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_bottom_padding"
     android:paddingLeft="@dimen/dynamic_grid_cell_padding_x"
     android:paddingRight="@dimen/dynamic_grid_cell_padding_x"
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 0aaedca..79fa9f1 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -43,6 +43,10 @@
     <string name="out_of_space" msgid="4691004494942118364">"Der er ikke mere plads på denne startskærm."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Der er ikke mere plads i bakken Foretrukne"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Liste med apps"</string>
+    <!-- no translation found for all_apps_button_personal_label (1315764287305224468) -->
+    <skip />
+    <!-- no translation found for all_apps_button_work_label (7270707118948892488) -->
+    <skip />
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Startskærm"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Fjern"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Afinstaller"</string>
@@ -77,19 +81,18 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Baggrunde"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Indstillinger for startskærmen"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Deaktiveret af din administrator"</string>
-    <string name="accessibility_action_overview" msgid="6257665857640347026">"Oversigt"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"Tillad rotation af startskærmen"</string>
-    <string name="allow_rotation_desc" msgid="8662546029078692509">"Når telefonen roteres"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Den aktuelle indstilling for visning tillader ikke rotation"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"Underretningscirkler"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"Til"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"Fra"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Kræver adgang til underretninger"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Hvis du vil se underretningscirkler, skal du aktivere appunderretninger for <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Skift indstillinger"</string>
+    <string name="icon_badging_service_title" msgid="2309733118428242174">"Vis underretningscirkler"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Føj ikon til startskærmen"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For nye apps"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Skift ikonform"</string>
+    <!-- no translation found for icon_shape_override_label_location (3841607380657692863) -->
+    <skip />
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Brug systemstandarden"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"Kvadrat"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"Kvadrat med runde hjørner"</string>
@@ -119,9 +122,6 @@
     <string name="create_folder_with" msgid="4050141361160214248">"Opret mappe med: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"Mappen blev oprettet"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"Flyt til startskærmen"</string>
-    <string name="action_move_screen_left" msgid="8854216831569401665">"Flyt skærmen til venstre"</string>
-    <string name="action_move_screen_right" msgid="329334910274311123">"Flyt skærmen til højre"</string>
-    <string name="screen_moved" msgid="266230079505650577">"Skærmen er flyttet"</string>
     <string name="action_resize" msgid="1802976324781771067">"Tilpas størrelse"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"Øg bredden"</string>
     <string name="action_increase_height" msgid="459390020612501122">"Øg højden"</string>
@@ -137,8 +137,7 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Arbejde"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Arbejdsprofil"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Find arbejdsapps her"</string>
-    <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
-    <skip />
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Alle arbejdsapps har et badge og beskyttes af din organisation. Flyt apps til din startskærm, så du nemmere kan få adgang til dem."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"Administreret af din organisation"</string>
     <string name="work_mode_off_label" msgid="3194894777601421047">"Underretninger og apps er slået fra"</string>
 </resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 21c9706..b07d9a2 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -43,6 +43,8 @@
     <string name="out_of_space" msgid="4691004494942118364">"Δεν υπάρχει χώρος σε αυτήν την αρχική οθόνη."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Δεν υπάρχει επιπλέον χώρος στην περιοχή Αγαπημένα"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Λίστα εφαρμογών"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Λίστα προσωπικών εφαρμογών"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"Λίστα εφαρμογών εργασίας"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Αρχική οθόνη"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Κατάργηση"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Απεγκατάσταση"</string>
@@ -77,19 +79,17 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Ταπετσαρίες"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Ρυθμίσεις Αρχικής σελίδας"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Απενεργοποιήθηκε από τον διαχειριστή σας"</string>
-    <string name="accessibility_action_overview" msgid="6257665857640347026">"Επισκόπηση"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"Να επιτρέπεται η περιστροφή της αρχικής οθόνης"</string>
-    <string name="allow_rotation_desc" msgid="8662546029078692509">"Όταν το τηλέφωνο περιστρέφεται"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Η τρέχουσα ρύθμιση οθόνης δεν επιτρέπει την περιστροφή"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"Κουκκίδες ειδοποίησης"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"Ενεργή"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"Ανενεργή"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Απαιτείται πρόσβαση στις ειδοποιήσεις"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Για να εμφανιστούν οι Κουκκίδες ειδοποίησης, ενεργοποιήστε τις κουκκίδες εφαρμογής για την εφαρμογή <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Αλλαγή ρυθμίσεων"</string>
+    <string name="icon_badging_service_title" msgid="2309733118428242174">"Εμφάνιση κουκκίδων ειδοποίησης"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Προσθήκη εικονιδίου στην Αρχική οθόνη"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Για νέες εφαρμογές"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Αλλαγή σχήματος εικονιδίου"</string>
+    <string name="icon_shape_override_label_location" msgid="3841607380657692863">"στην Αρχική οθόνη"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Χρήση προεπιλογής συστήματος"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"Τετράγωνο"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"Στρογγυλεμένο τετράγωνο"</string>
@@ -119,9 +119,6 @@
     <string name="create_folder_with" msgid="4050141361160214248">"Δημιουργία φακέλου με: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"Δημιουργήθηκε φάκελος"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"Μετακίνηση Αρχικής οθόνης"</string>
-    <string name="action_move_screen_left" msgid="8854216831569401665">"Μετακίνηση οθόνης αριστερά"</string>
-    <string name="action_move_screen_right" msgid="329334910274311123">"Μετακίνηση οθόνης δεξιά"</string>
-    <string name="screen_moved" msgid="266230079505650577">"Η οθόνη μετακινήθηκε"</string>
     <string name="action_resize" msgid="1802976324781771067">"Προσαρμογή μεγέθους"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"Αύξηση του πλάτους"</string>
     <string name="action_increase_height" msgid="459390020612501122">"Αύξηση του ύψους"</string>
@@ -137,8 +134,7 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Εργασίας"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Προφίλ εργασίας"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Βρείτε όλες τις εφαρμογές εργασίας εδώ"</string>
-    <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
-    <skip />
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Κάθε εφαρμογή εργασίας φέρει ένα σήμα και διατηρείται ασφαλής από τον οργανισμό σας. Μετακινήστε τις εφαρμογές εργασίας στην Αρχική οθόνη, για να έχετε πιο εύκολη πρόσβαση."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"Διαχειριζόμενο από τον οργανισμό σας"</string>
     <string name="work_mode_off_label" msgid="3194894777601421047">"Οι ειδοποιήσεις και οι εφαρμογές είναι απενεργοποιημένες"</string>
 </resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index c84efc7..0e1004a 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -43,6 +43,8 @@
     <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Apps list"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Personal apps list"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"Work apps list"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Home"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Remove"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
@@ -77,19 +79,17 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Disabled by your admin"</string>
-    <string name="accessibility_action_overview" msgid="6257665857640347026">"Overview"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"Allow Homescreen rotation"</string>
-    <string name="allow_rotation_desc" msgid="8662546029078692509">"When phone is rotated"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Current display setting doesn\'t permit rotation"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"Notification dots"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"On"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"Off"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Notification access needed"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"To show Notification Dots, turn on app notifications for <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Change settings"</string>
+    <string name="icon_badging_service_title" msgid="2309733118428242174">"Show notification dots"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Add icon to Home screen"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For new apps"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Change icon shape"</string>
+    <string name="icon_shape_override_label_location" msgid="3841607380657692863">"on Home screen"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Use system default"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"Square"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"Squircle"</string>
@@ -119,9 +119,6 @@
     <string name="create_folder_with" msgid="4050141361160214248">"Create folder with: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"Folder created"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"Move to Home screen"</string>
-    <string name="action_move_screen_left" msgid="8854216831569401665">"Move screen to left"</string>
-    <string name="action_move_screen_right" msgid="329334910274311123">"Move screen to right"</string>
-    <string name="screen_moved" msgid="266230079505650577">"Screen moved"</string>
     <string name="action_resize" msgid="1802976324781771067">"Re-size"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"Increase width"</string>
     <string name="action_increase_height" msgid="459390020612501122">"Increase height"</string>
@@ -137,8 +134,7 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Work profile"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Find work apps here"</string>
-    <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
-    <skip />
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Each work app has a badge and is kept secure by your organisation. Move apps to your Home screen for easier access."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"Managed by your organisation"</string>
     <string name="work_mode_off_label" msgid="3194894777601421047">"Notifications and apps are off"</string>
 </resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index c84efc7..0e1004a 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -43,6 +43,8 @@
     <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Apps list"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Personal apps list"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"Work apps list"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Home"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Remove"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
@@ -77,19 +79,17 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Disabled by your admin"</string>
-    <string name="accessibility_action_overview" msgid="6257665857640347026">"Overview"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"Allow Homescreen rotation"</string>
-    <string name="allow_rotation_desc" msgid="8662546029078692509">"When phone is rotated"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Current display setting doesn\'t permit rotation"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"Notification dots"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"On"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"Off"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Notification access needed"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"To show Notification Dots, turn on app notifications for <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Change settings"</string>
+    <string name="icon_badging_service_title" msgid="2309733118428242174">"Show notification dots"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Add icon to Home screen"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For new apps"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Change icon shape"</string>
+    <string name="icon_shape_override_label_location" msgid="3841607380657692863">"on Home screen"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Use system default"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"Square"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"Squircle"</string>
@@ -119,9 +119,6 @@
     <string name="create_folder_with" msgid="4050141361160214248">"Create folder with: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"Folder created"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"Move to Home screen"</string>
-    <string name="action_move_screen_left" msgid="8854216831569401665">"Move screen to left"</string>
-    <string name="action_move_screen_right" msgid="329334910274311123">"Move screen to right"</string>
-    <string name="screen_moved" msgid="266230079505650577">"Screen moved"</string>
     <string name="action_resize" msgid="1802976324781771067">"Re-size"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"Increase width"</string>
     <string name="action_increase_height" msgid="459390020612501122">"Increase height"</string>
@@ -137,8 +134,7 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Work profile"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Find work apps here"</string>
-    <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
-    <skip />
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Each work app has a badge and is kept secure by your organisation. Move apps to your Home screen for easier access."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"Managed by your organisation"</string>
     <string name="work_mode_off_label" msgid="3194894777601421047">"Notifications and apps are off"</string>
 </resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index c84efc7..0e1004a 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -43,6 +43,8 @@
     <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Apps list"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Personal apps list"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"Work apps list"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Home"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Remove"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
@@ -77,19 +79,17 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Disabled by your admin"</string>
-    <string name="accessibility_action_overview" msgid="6257665857640347026">"Overview"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"Allow Homescreen rotation"</string>
-    <string name="allow_rotation_desc" msgid="8662546029078692509">"When phone is rotated"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Current display setting doesn\'t permit rotation"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"Notification dots"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"On"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"Off"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Notification access needed"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"To show Notification Dots, turn on app notifications for <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Change settings"</string>
+    <string name="icon_badging_service_title" msgid="2309733118428242174">"Show notification dots"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Add icon to Home screen"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For new apps"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Change icon shape"</string>
+    <string name="icon_shape_override_label_location" msgid="3841607380657692863">"on Home screen"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Use system default"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"Square"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"Squircle"</string>
@@ -119,9 +119,6 @@
     <string name="create_folder_with" msgid="4050141361160214248">"Create folder with: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"Folder created"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"Move to Home screen"</string>
-    <string name="action_move_screen_left" msgid="8854216831569401665">"Move screen to left"</string>
-    <string name="action_move_screen_right" msgid="329334910274311123">"Move screen to right"</string>
-    <string name="screen_moved" msgid="266230079505650577">"Screen moved"</string>
     <string name="action_resize" msgid="1802976324781771067">"Re-size"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"Increase width"</string>
     <string name="action_increase_height" msgid="459390020612501122">"Increase height"</string>
@@ -137,8 +134,7 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Work profile"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Find work apps here"</string>
-    <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
-    <skip />
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Each work app has a badge and is kept secure by your organisation. Move apps to your Home screen for easier access."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"Managed by your organisation"</string>
     <string name="work_mode_off_label" msgid="3194894777601421047">"Notifications and apps are off"</string>
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 0fb401a..b2be7c4 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -43,6 +43,8 @@
     <string name="out_of_space" msgid="4691004494942118364">"No queda espacio en la pantalla de inicio."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"La bandeja de favoritos está completa"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lista de aplicaciones"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Lista de aplicaciones personales"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"Lista de aplicaciones del trabajo"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Inicio"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Quitar"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalar"</string>
@@ -77,19 +79,17 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Fondos de pantalla"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Ajustes de Home"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Inhabilitada por el administrador"</string>
-    <string name="accessibility_action_overview" msgid="6257665857640347026">"Visión general"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"Permitir rotación de la pantalla de inicio"</string>
-    <string name="allow_rotation_desc" msgid="8662546029078692509">"Al girar el teléfono"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"La configuración de pantalla actual no permite girar la pantalla"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"Puntos de notificación"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"Activada"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"Desactivada"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Se necesita acceso a las notificaciones"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Para mostrar burbujas de notificación, activa las notificaciones de <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Cambiar ajustes"</string>
+    <string name="icon_badging_service_title" msgid="2309733118428242174">"Mostrar burbujas de notificación"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Añadir icono a la pantalla de inicio"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para aplicaciones nuevas"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Cambiar forma de los iconos"</string>
+    <string name="icon_shape_override_label_location" msgid="3841607380657692863">"en la pantalla de inicio"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Usar opción predeterminada del sistema"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"Cuadrado"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"Cuadrado con esquinas redondeadas"</string>
@@ -119,9 +119,6 @@
     <string name="create_folder_with" msgid="4050141361160214248">"Crear carpeta con: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"Carpeta creada"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"Mover a la pantalla de inicio"</string>
-    <string name="action_move_screen_left" msgid="8854216831569401665">"Mover pantalla a la izquierda"</string>
-    <string name="action_move_screen_right" msgid="329334910274311123">"Mover pantalla a la derecha"</string>
-    <string name="screen_moved" msgid="266230079505650577">"Pantalla movida"</string>
     <string name="action_resize" msgid="1802976324781771067">"Modificar tamaño"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"Aumentar ancho"</string>
     <string name="action_increase_height" msgid="459390020612501122">"Aumentar altura"</string>
@@ -137,8 +134,7 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Trabajo"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de trabajo"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Aplicaciones de trabajo"</string>
-    <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
-    <skip />
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Cada aplicación de trabajo tiene una insignia y está protegida por tu organización. Mueve las aplicaciones a la pantalla de inicio para acceder a ellas más fácilmente."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"Administrada por tu organización"</string>
     <string name="work_mode_off_label" msgid="3194894777601421047">"Las notificaciones y las aplicaciones están desactivadas"</string>
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 89e57e7..5794af1 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -43,6 +43,8 @@
     <string name="out_of_space" msgid="4691004494942118364">"Tidak ada ruang lagi pada layar Utama ini."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Tidak ada ruang tersisa di baki Favorit"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Daftar aplikasi"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Daftar aplikasi pribadi"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"Daftar aplikasi kantor"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Layar Utama"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Hapus"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstal"</string>
@@ -77,19 +79,17 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpaper"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Setelan layar Utama"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Dinonaktifkan oleh admin"</string>
-    <string name="accessibility_action_overview" msgid="6257665857640347026">"Ringkasan"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"Izinkan layar Utama diputar"</string>
-    <string name="allow_rotation_desc" msgid="8662546029078692509">"Saat ponsel diputar"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Setelan Tampilan Saat Ini tidak memungkinkan putaran"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"Titik notifikasi"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"Aktif"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"Nonaktif"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Perlu akses notifikasi"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Guna menampilkan Titik Notifikasi, aktifkan notifikasi aplikasi untuk <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Ubah setelan"</string>
+    <string name="icon_badging_service_title" msgid="2309733118428242174">"Tampilkan titik notifikasi"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Tambahkan ikon ke Layar utama"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Untuk aplikasi baru"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Ubah bentuk ikon"</string>
+    <string name="icon_shape_override_label_location" msgid="3841607380657692863">"di layar Utama"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Gunakan default sistem"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"Persegi"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"Persegi bundar"</string>
@@ -119,9 +119,6 @@
     <string name="create_folder_with" msgid="4050141361160214248">"Buat folder dengan: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"Folder dibuat"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"Pindahkan ke layar Utama"</string>
-    <string name="action_move_screen_left" msgid="8854216831569401665">"Pindahkan layar ke kiri"</string>
-    <string name="action_move_screen_right" msgid="329334910274311123">"Pindahkan layar ke kanan"</string>
-    <string name="screen_moved" msgid="266230079505650577">"Layar dipindahkan"</string>
     <string name="action_resize" msgid="1802976324781771067">"Ubah ukuran"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"Tambahi lebar"</string>
     <string name="action_increase_height" msgid="459390020612501122">"Tambahi tinggi"</string>
@@ -137,8 +134,7 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Kantor"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil kerja"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Temukan aplikasi kerja di sini"</string>
-    <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
-    <skip />
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Setiap aplikasi kerja memiliki badge dan dibuat tetap aman oleh organisasi. Pindahkan aplikasi ke Layar utama untuk memudahkan akses."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"Dikelola oleh organisasi"</string>
     <string name="work_mode_off_label" msgid="3194894777601421047">"Notifikasi dan aplikasi nonaktif"</string>
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 8584629..c220dec 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -43,6 +43,8 @@
     <string name="out_of_space" msgid="4691004494942118364">"Spazio nella schermata Home esaurito."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Spazio esaurito nella barra dei Preferiti"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Elenco di app"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Elenco di app personali"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"Elenco di app di lavoro"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Home page"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Rimuovi"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Disinstalla"</string>
@@ -77,19 +79,17 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Sfondi"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Impostazioni Home"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Disattivata dall\'amministratore"</string>
-    <string name="accessibility_action_overview" msgid="6257665857640347026">"Panoramica"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"Consenti rotazione della schermata Home"</string>
-    <string name="allow_rotation_desc" msgid="8662546029078692509">"Con il telefono ruotato"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"L\'impostazione corrente del display non consente la rotazione"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"Indicatori notifica"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"Attiva"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"Non attiva"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Accesso alle notifiche necessario"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Per mostrare gli indicatori di notifica, attiva le notifiche per l\'app <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Modifica impostazioni"</string>
+    <string name="icon_badging_service_title" msgid="2309733118428242174">"Mostra indicatori di notifica"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Aggiungi icone alla schermata Home"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Per le nuove app"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Cambia la forma delle icone"</string>
+    <string name="icon_shape_override_label_location" msgid="3841607380657692863">"nella schermata Home"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Usa impostazione predefinita di sistema"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"Quadrato"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"Supercerchio"</string>
@@ -119,9 +119,6 @@
     <string name="create_folder_with" msgid="4050141361160214248">"Crea cartella con: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"Cartella creata"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"Sposta nella schermata Home"</string>
-    <string name="action_move_screen_left" msgid="8854216831569401665">"Sposta schermata a sinistra"</string>
-    <string name="action_move_screen_right" msgid="329334910274311123">"Sposta schermata a destra"</string>
-    <string name="screen_moved" msgid="266230079505650577">"Schermata spostata"</string>
     <string name="action_resize" msgid="1802976324781771067">"Ridimensiona"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"Aumenta larghezza"</string>
     <string name="action_increase_height" msgid="459390020612501122">"Aumenta altezza"</string>
@@ -137,8 +134,7 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Lavoro"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Profilo di lavoro"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Qui puoi trovare le tue app di lavoro"</string>
-    <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
-    <skip />
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Ogni app di lavoro è contrassegnata da un badge e viene tenuta al sicuro dalla tua organizzazione. Sposta le app nella schermata Home per accedervi più facilmente."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"Gestito dalla tua organizzazione"</string>
     <string name="work_mode_off_label" msgid="3194894777601421047">"Le notifiche e le app non sono attive"</string>
 </resources>
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index 40ffffe..6cf23ad 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -25,7 +25,6 @@
     <dimen name="fastscroll_popup_text_size">24dp</dimen>
 
     <!-- Dynamic grid -->
-    <dimen name="dynamic_grid_overview_bar_item_width">120dp</dimen>
     <dimen name="dynamic_grid_min_page_indicator_size">48dp</dimen>
     <dimen name="dynamic_grid_icon_drawable_padding">4dp</dimen>
 
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index c004318..e862116 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -43,6 +43,8 @@
     <string name="out_of_space" msgid="4691004494942118364">"ဤပင်မမျက်နှာစာတွင် နေရာလွတ် မကျန်တော့ပါ"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"အနှစ်သက်ဆုံးများ ထားရာတွင် နေရာလွတ် မကျန်တော့ပါ"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"အက်ပ်စာရင်း"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"ကိုယ်ရေးကိုယ်တာ အက်ပ်စာရင်း"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"အလုပ်သုံး အက်ပ်စာရင်း"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"ပင်မစာမျက်နှာ"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"ဖယ်ရှားမည်"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ဖယ်ထုတ်မည်"</string>
@@ -77,19 +79,17 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"နောက်ခံများ"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"ပင်မဆက်တင်များ"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"သင့်စီမံခန့်ခွဲသူက ပိတ်လိုက်ပါသည်"</string>
-    <string name="accessibility_action_overview" msgid="6257665857640347026">"ခြုံငုံသုံးသပ်ချက်"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"ပင်မစာမျက်နှာလှည့်ခြင်းကို ခွင့်ပြုပါ"</string>
-    <string name="allow_rotation_desc" msgid="8662546029078692509">"ဖုန်းကိုလှည့်ထားစဉ်"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"လက်ရှိ မြင်ကွင်းဆက်တင်တွင် မြင်ကွင်းကို လှည့်ခွင့်မပေးပါ"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"အကြောင်းကြားချက်အမှတ်အသားများ"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"ဖွင့်ထားသည်"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"ပိတ်ထားသည်"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"အကြောင်းကြားချက် အသုံးပြုခွင့် လိုအပ်သည်"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"အကြောင်းကြားချက် အစက်များကို ပြသရန် <xliff:g id="NAME">%1$s</xliff:g> အတွက် အက်ပ်အကြောင်းကြားချက်များကို ဖွင့်ပါ"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"ဆက်တင်များ ပြောင်းရန်"</string>
+    <string name="icon_badging_service_title" msgid="2309733118428242174">"အကြောင်းကြားချက် အမှတ်အသားများကို ပြရန်"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ပင်မစာမျက်နှာသို့ သင်္ကေတပုံ ထည့်ရန်"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"အက်ပ်အသစ်များအတွက်"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"သင်္ကေတပုံစံကို ပြောင်းရန်"</string>
+    <string name="icon_shape_override_label_location" msgid="3841607380657692863">"\'ပင်မမျက်နှာပြင်\' ပေါ်တွင်"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"စနစ်၏ မူရင်းပုံကို အသုံးပြုရန်"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"လေးထောင့်"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"စတုရန်းမကျ စက်ဝိုင်းမကျပုံ"</string>
@@ -119,9 +119,6 @@
     <string name="create_folder_with" msgid="4050141361160214248">"ဖိုလ်ဒါ ပြုလုပ်ရန်- <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"ဖိုလ်ဒါ ပြုလုပ်ပြီး"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"ပင်မမျက်နှာပြင်သို့ ရွှေ့ပါ"</string>
-    <string name="action_move_screen_left" msgid="8854216831569401665">"မျက်နှာပြင် ဘယ်ဘက်သို့ ရွှေ့ပါ"</string>
-    <string name="action_move_screen_right" msgid="329334910274311123">"မျက်နှာပြင် ညာဘက်သို့ ရွှေ့ပါ"</string>
-    <string name="screen_moved" msgid="266230079505650577">"ဖန်မျက်နှာပြင် ပြောင်းရွှေ့ပြီး၏"</string>
     <string name="action_resize" msgid="1802976324781771067">"အရွယ်အစားပြောင်းပါ"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"အကျယ်အား တိုးပါ"</string>
     <string name="action_increase_height" msgid="459390020612501122">"အမြင့်အား တိုးပါ"</string>
@@ -137,8 +134,7 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"အလုပ်"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"အလုပ်ပရိုဖိုင်"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"အလုပ်အက်ပ်များကို ဤနေရာတွင်ရှာဖွေပါ"</string>
-    <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
-    <skip />
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"အလုပ်အက်ပ်တိုင်းတွင် တံဆိပ် တစ်ခုစီရှိပြီး သင်၏ အဖွဲ့အစည်းက လုံခြုံအောင် ထားရှိပါသည်။ အသုံးပြုရ ပိုမိုလွယ်ကူစေရန် အက်ပ်များကို သင်၏ ပင်မမျက်နှာပြင်သို့ ရွှေ့ပါ။"</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"သင်၏ အဖွဲ့အစည်းက စီမံခန့်ခွဲထားပါသည်"</string>
     <string name="work_mode_off_label" msgid="3194894777601421047">"အကြောင်းကြားချက်များနှင့် အက်ပ်များကို ပိတ်ထားသည်"</string>
 </resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 77aeee2..e9b4cc9 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -43,6 +43,8 @@
     <string name="out_of_space" msgid="4691004494942118364">"Brak miejsca na tym ekranie głównym."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Brak miejsca w Ulubionych"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lista aplikacji"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Lista aplikacji osobistych"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"Lista aplikacji do pracy"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Ekran główny"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Usuń"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Odinstaluj"</string>
@@ -79,19 +81,17 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Tapety"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Ustawienia strony głównej"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Funkcja wyłączona przez administratora"</string>
-    <string name="accessibility_action_overview" msgid="6257665857640347026">"Przegląd"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"Zezwalaj na obrót ekranu głównego"</string>
-    <string name="allow_rotation_desc" msgid="8662546029078692509">"Po obróceniu telefonu"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Obecne ustawienia wyświetlania nie pozwalają na obrót ekranu"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"Plakietki z powiadomieniami"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"Włączono"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"Wyłączono"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Wymagany jest dostęp do powiadomień"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Aby pokazać plakietki z powiadomieniami, włącz powiadomienia aplikacji <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Zmień ustawienia"</string>
+    <string name="icon_badging_service_title" msgid="2309733118428242174">"Pokaż plakietki z powiadomieniami"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Dodaj ikonę do ekranu głównego"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"W przypadku nowych aplikacji"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Zmień kształt ikon"</string>
+    <string name="icon_shape_override_label_location" msgid="3841607380657692863">"na ekranie głównym"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Użyj ustawienia domyślnego"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"Kwadrat"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"Zaokrąglony kwadrat"</string>
@@ -121,9 +121,6 @@
     <string name="create_folder_with" msgid="4050141361160214248">"Utwórz folder z: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"Folder został utworzony"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"Przenieś na ekran główny"</string>
-    <string name="action_move_screen_left" msgid="8854216831569401665">"Przenieś ekran w lewo"</string>
-    <string name="action_move_screen_right" msgid="329334910274311123">"Przenieś ekran w prawo"</string>
-    <string name="screen_moved" msgid="266230079505650577">"Ekran został przeniesiony"</string>
     <string name="action_resize" msgid="1802976324781771067">"Zmień rozmiar"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"Zwiększ szerokość"</string>
     <string name="action_increase_height" msgid="459390020612501122">"Zwiększ wysokość"</string>
@@ -139,8 +136,7 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Praca"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil służbowy"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Aplikacje do pracy"</string>
-    <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
-    <skip />
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Każda aplikacja do pracy ma plakietkę, a o jej bezpieczeństwo dba Twoja organizacja. Aplikacje można przenieść na ekran główny, by były łatwiej dostępne."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"Profil zarządzany przez Twoją organizację"</string>
     <string name="work_mode_off_label" msgid="3194894777601421047">"Powiadomienia i aplikacje są wyłączone"</string>
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index cd473fa..d6f67b3 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -43,6 +43,8 @@
     <string name="out_of_space" msgid="4691004494942118364">"Sem espaço suficiente neste Ecrã principal."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Não existe mais espaço no tabuleiro de Favoritos"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lista de aplicações"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Lista de aplicações pessoais"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"Lista de aplicações de trabalho"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Ecrã principal"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Remover"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstalar"</string>
@@ -77,19 +79,17 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Imagens de fundo"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Definições da página inicial"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Desativada pelo gestor"</string>
-    <string name="accessibility_action_overview" msgid="6257665857640347026">"Vista geral"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"Permitir rotação do ecrã principal"</string>
-    <string name="allow_rotation_desc" msgid="8662546029078692509">"Quando o telemóvel é rodado"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"A definição de visualização atual não permite a rotação"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"Pontos de notificação"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"Ativada"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"Desativada"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Acesso a notificações necessário"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Para mostrar os Pontos de notificação, ative as notificações de aplicações para o <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Alterar definições"</string>
+    <string name="icon_badging_service_title" msgid="2309733118428242174">"Mostrar pontos de notificação"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Adicionar ícone ao ecrã principal"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para novas aplicações"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Alterar forma do ícone"</string>
+    <string name="icon_shape_override_label_location" msgid="3841607380657692863">"no ecrã principal"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Utilizar a predefinição do sistema"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"Quadrado"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"Quadrado e círculo"</string>
@@ -119,9 +119,6 @@
     <string name="create_folder_with" msgid="4050141361160214248">"Criar pasta com: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"Pasta criada"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"Mover para o Ecrã principal"</string>
-    <string name="action_move_screen_left" msgid="8854216831569401665">"Mover ecrã para a esquerda"</string>
-    <string name="action_move_screen_right" msgid="329334910274311123">"Mover ecrã para a direita"</string>
-    <string name="screen_moved" msgid="266230079505650577">"Ecrã movido"</string>
     <string name="action_resize" msgid="1802976324781771067">"Redimensionar"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"Aumentar largura"</string>
     <string name="action_increase_height" msgid="459390020612501122">"Aumentar altura"</string>
@@ -137,8 +134,7 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Trabalho"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de trabalho"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Encontrar as aplicações de trabalho aqui"</string>
-    <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
-    <skip />
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Cada aplicação de trabalho apresenta um emblema, pelo que a sua entidade a mantém em segurança. Pode mover as aplicações para o ecrã principal para facilitar o acesso."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"Gerido pela sua entidade"</string>
     <string name="work_mode_off_label" msgid="3194894777601421047">"As notificações e as aplicações estão desativadas."</string>
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 495ba4c..fdbcd5b 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -43,6 +43,8 @@
     <string name="out_of_space" msgid="4691004494942118364">"Nu mai este loc pe acest Ecran de pornire."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Spațiu epuizat în bara Preferate"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lista de aplicații"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Lista de aplicații personale"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"Lista de aplicații de serviciu"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Ecran de pornire"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Eliminați"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Dezinstalați"</string>
@@ -78,19 +80,17 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Imagini de fundal"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Setări pentru ecranul de pornire"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Dezactivată de administrator"</string>
-    <string name="accessibility_action_overview" msgid="6257665857640347026">"Prezentare generală"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"Permiteți rotirea ecranului de pornire"</string>
-    <string name="allow_rotation_desc" msgid="8662546029078692509">"Când telefonul este rotit"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Setarea actuală a afișajului nu permite rotirea"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"Puncte de notificare"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"Activat"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"Dezactivat"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Este necesar accesul la notificări"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Pentru a afișa punctele de notificare, activați notificările din aplicație pentru <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Modificați setările"</string>
+    <string name="icon_badging_service_title" msgid="2309733118428242174">"Afișați punctele de notificare"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Adaugă pictograme în ecranul de pornire"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pentru aplicații noi"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Schimbați forma pictogramei"</string>
+    <string name="icon_shape_override_label_location" msgid="3841607380657692863">"pe ecranul de pornire"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Folosiți setarea prestabilită a sistemului"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"Pătrat"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"Pătrat cu colțuri rotunjite"</string>
@@ -120,9 +120,6 @@
     <string name="create_folder_with" msgid="4050141361160214248">"Creați dosar cu: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"Dosar creat"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"Mutați pe ecranul de pornire"</string>
-    <string name="action_move_screen_left" msgid="8854216831569401665">"Mutați ecranul la stânga"</string>
-    <string name="action_move_screen_right" msgid="329334910274311123">"Mutați ecranul la dreapta"</string>
-    <string name="screen_moved" msgid="266230079505650577">"Ecran mutat"</string>
     <string name="action_resize" msgid="1802976324781771067">"Redimensionați"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"Creșteți lățimea"</string>
     <string name="action_increase_height" msgid="459390020612501122">"Creșteți înălțimea"</string>
@@ -138,8 +135,7 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Profesionale"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil de serviciu"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Găsiți aplicații de serviciu aici"</string>
-    <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
-    <skip />
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Fiecare aplicație de serviciu are o insignă și este păstrată în siguranță de organizația dvs. Mutați aplicațiile pe ecranul de pornire pentru acces mai ușor."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"Gestionat de organizația dvs."</string>
     <string name="work_mode_off_label" msgid="3194894777601421047">"Notificările și aplicațiile sunt dezactivate"</string>
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 85e814b..9d153f4 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -43,6 +43,8 @@
     <string name="out_of_space" msgid="4691004494942118364">"Det finns inte plats för mer på den här startskärmen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritfältet är fullt"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Applista"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Listan Personliga appar"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"Listan Jobbappar"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Startskärm"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Ta bort"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Avinstallera"</string>
@@ -77,19 +79,17 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Bakgrunder"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Inställningar för startsidan"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Inaktiverat av administratören"</string>
-    <string name="accessibility_action_overview" msgid="6257665857640347026">"Översikt"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"Tillåt rotering av startskärmen"</string>
-    <string name="allow_rotation_desc" msgid="8662546029078692509">"När mobilen vrids"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Rotering tillåts inte i de nuvarande skärminställningarna"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"Aviseringsprickar"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"På"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"Av"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Åtkomst till aviseringar krävs"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Aktivera appaviseringar för <xliff:g id="NAME">%1$s</xliff:g> om du vill att aviseringsprickar ska visas"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Ändra inställningar"</string>
+    <string name="icon_badging_service_title" msgid="2309733118428242174">"Visa aviseringsprickar"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Lägg till ikonen på startskärmen"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"För nya appar"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Ändra form på ikoner"</string>
+    <string name="icon_shape_override_label_location" msgid="3841607380657692863">"på startskärmen"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Använd systemstandard"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"Kvadrat"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"Kvirkel"</string>
@@ -119,9 +119,6 @@
     <string name="create_folder_with" msgid="4050141361160214248">"Skapa mapp med: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"Mappen har skapats"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"Flytta till startskärmen"</string>
-    <string name="action_move_screen_left" msgid="8854216831569401665">"Flytta skärmen till vänster"</string>
-    <string name="action_move_screen_right" msgid="329334910274311123">"Flytta skärmen till höger"</string>
-    <string name="screen_moved" msgid="266230079505650577">"Skärmen har flyttats"</string>
     <string name="action_resize" msgid="1802976324781771067">"Ändra storlek"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"Öka bredden"</string>
     <string name="action_increase_height" msgid="459390020612501122">"Öka höjden"</string>
@@ -137,8 +134,7 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Arbete"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Jobbprofil"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Här hittar du jobbappar"</string>
-    <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
-    <skip />
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Alla jobbappar har ett märke och organisationen ser till att de är skyddade. Flytta apparna till startskärmen så kommer du åt dem lättare."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"Hanteras av organisationen"</string>
     <string name="work_mode_off_label" msgid="3194894777601421047">"Aviseringar och appar är inaktiverade"</string>
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index a9dc8a6..da0bb8e 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -43,6 +43,8 @@
     <string name="out_of_space" msgid="4691004494942118364">"Hakuna nafasi katika skrini hii ya Mwanzo."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Hakuna nafasi zaidi katika treya ya Vipendeleo"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Orodha ya programu"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Orodha ya programu za binafsi"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"Orodha ya programu za kazini"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"Mwanzo"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Ondoa"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Ondoa"</string>
@@ -79,19 +81,17 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Mandhari"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Mipangilio ya ukurasa wa mwanzo"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Imezimwa na msimamizi wako"</string>
-    <string name="accessibility_action_overview" msgid="6257665857640347026">"Muhtasari"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"Ruhusu kuzungusha skrini ya Kwanza"</string>
-    <string name="allow_rotation_desc" msgid="8662546029078692509">"Simu inapozungushwa"</string>
-    <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Mipangilio ya sasa ya sehemu ya Onyesho hairuhusu kuzungusha"</string>
     <string name="icon_badging_title" msgid="874121399231955394">"Vitone vya arifa"</string>
     <string name="icon_badging_desc_on" msgid="2627952638544674079">"Imewashwa"</string>
     <string name="icon_badging_desc_off" msgid="5503319969924580241">"Imezimwa"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Inahitaji idhini ya kufikia arifa"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Ili kuonyesha Vitone vya Arifa, washa kipengele cha arifa za programu katika <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Badilisha mipangilio"</string>
+    <string name="icon_badging_service_title" msgid="2309733118428242174">"Onyesha kitone cha arifa"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Ongeza aikoni kwenye Skrini ya kwanza"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Kwa ajili ya programu mpya"</string>
     <string name="icon_shape_override_label" msgid="2977264953998281004">"Badilisha umbo la aikoni"</string>
+    <string name="icon_shape_override_label_location" msgid="3841607380657692863">"kwenye Skrini ya mwanzo"</string>
     <string name="icon_shape_system_default" msgid="1709762974822753030">"Tumia umbo chaguo-msingi la mfumo"</string>
     <string name="icon_shape_square" msgid="633575066111622774">"Mraba"</string>
     <string name="icon_shape_squircle" msgid="5658049910802669495">"Mstatili wenye pembe duara"</string>
@@ -121,9 +121,6 @@
     <string name="create_folder_with" msgid="4050141361160214248">"Unda folda ukitumia: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"Folda imeundwa"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"Hamishia Skrini ya kwanza"</string>
-    <string name="action_move_screen_left" msgid="8854216831569401665">"Sogeza skrini kushoto"</string>
-    <string name="action_move_screen_right" msgid="329334910274311123">"Sogeza skrini kulia"</string>
-    <string name="screen_moved" msgid="266230079505650577">"Skrini imesogezwa"</string>
     <string name="action_resize" msgid="1802976324781771067">"Badilisha ukubwa"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"Ongeza upana"</string>
     <string name="action_increase_height" msgid="459390020612501122">"Ongeza urefu"</string>
@@ -139,8 +136,7 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Kazini"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Wasifu wa kazini"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Pata programu za kazi hapa"</string>
-    <!-- no translation found for bottom_work_tab_user_education_body (2818107472360579152) -->
-    <skip />
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Kila programu ya kazi ina beji na hulindwa na shirika lako. Hamishia programu kwenye skrini yako ya kwanza ili uzifikie kwa urahisi."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"Inasimamiwa na shirika lako"</string>
     <string name="work_mode_off_label" msgid="3194894777601421047">"Vipenge vya arifa na programu vimezimwa"</string>
 </resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 3f727cf..a40afe1 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -119,6 +119,10 @@
     <!-- Tag id used for view scrim -->
     <item type="id" name="view_scrim" />
 
+    <!-- View IDs to store item highlight information -->
+    <item type="id" name="view_unhighlight_background" />
+    <item type="id" name="view_highlighted" />
+
 <!-- Popup items -->
     <integer name="config_popupOpenCloseDuration">150</integer>
     <integer name="config_popupArrowOpenDuration">80</integer>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 9f4233c..f8f9c2a 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -23,10 +23,6 @@
     <dimen name="dynamic_grid_min_page_indicator_size">24dp</dimen>
     <dimen name="dynamic_grid_page_indicator_line_height">1dp</dimen>
     <dimen name="dynamic_grid_icon_drawable_padding">8dp</dimen>
-    <dimen name="dynamic_grid_overview_min_icon_zone_height">80dp</dimen>
-    <dimen name="dynamic_grid_overview_max_icon_zone_height">120dp</dimen>
-    <dimen name="dynamic_grid_overview_bar_item_width">80dp</dimen>
-    <dimen name="dynamic_grid_overview_bar_spacer_width">25dp</dimen>
     <dimen name="dynamic_grid_workspace_top_padding">8dp</dimen>
     <dimen name="dynamic_grid_workspace_page_spacing">8dp</dimen>
     <!-- Minimum space between workspace and hotseat in spring loaded mode -->
@@ -227,5 +223,5 @@
     <dimen name="swipe_helper_falsing_threshold">70dp</dimen>
 
 <!-- Overview -->
-    <dimen name="options_menu_icon_size">48dp</dimen>
+    <dimen name="options_menu_icon_size">24dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d8b68e7..0b45b8e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -80,6 +80,9 @@
 
     <!-- All applications label -->
     <string name="all_apps_button_label">Apps list</string>
+    <string name="all_apps_button_personal_label">Personal apps list</string>
+    <string name="all_apps_button_work_label">Work apps list</string>
+
     <!-- Label for button in all applications label to go back home (to the workspace / desktop)
          for accessibilty (spoken when the button gets focus). -->
     <string name="all_apps_home_button_label">Home</string>
@@ -169,8 +172,6 @@
     <string name="settings_button_text">Home settings</string>
     <!-- Message shown when a feature is disabled by the administrator -->
     <string name="msg_disabled_by_admin">Disabled by your admin</string>
-    <!-- Text for custom accessibility action to go to the overview mode, where users can look and change the overall UI of the launcher. -->
-    <string name="accessibility_action_overview">Overview</string>
 
     <!-- Strings for settings -->
     <!-- Title for Notification dots setting. Tapping this will link to the system Notifications settings screen where the user can turn off notification dots globally. [CHAR LIMIT=50] -->
@@ -195,6 +196,8 @@
 
     <!-- Developer setting to change the shape of icons on home screen. [CHAR LIMIT=50] -->
     <string name="icon_shape_override_label">Change icon shape</string>
+    <!-- Subtext explaining that the icons will only be affected on the home screen. This text follows the actual icon action: Change icon shape, on Home screen [CHAR LIMIT=100] -->
+    <string name="icon_shape_override_label_location">on Home screen</string>
     <!-- Option to not change the icon shape on home screen and use the system default setting instead. [CHAR LIMIT=50] -->
     <string name="icon_shape_system_default">Use system default</string>
     <!-- Option to change the shape of the home screen icons to a square. [CHAR LIMIT=50] -->
@@ -278,15 +281,6 @@
     <!-- Accessibility action to move an item from folder to workspace. [CHAR_LIMIT=30] -->
     <string name="action_move_to_workspace">Move to Home screen</string>
 
-    <!-- Accessibility action to move an homescreen to the left. [CHAR_LIMIT=30] -->
-    <string name="action_move_screen_left">Move screen to left</string>
-
-    <!-- Accessibility action to move an homescreen to the right. [CHAR_LIMIT=30] -->
-    <string name="action_move_screen_right">Move screen to right</string>
-
-    <!-- Accessibility confirmation when a screen was moved. -->
-    <string name="screen_moved">Screen moved</string>
-
     <!-- Accessibility action to resize a widget. [CHAR_LIMIT=30] -->
     <string name="action_resize">Resize</string>
 
@@ -336,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/res/values/styles.xml b/res/values/styles.xml
index ac6a6b1..8076c80 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -112,7 +112,6 @@
         <item name="android:focusable">true</item>
         <item name="android:gravity">center_horizontal</item>
         <item name="android:singleLine">true</item>
-        <item name="android:ellipsize">marquee</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
         <item name="android:fontFamily">sans-serif-condensed</item>
 
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index fc5ce8f..f34cf0d 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -24,9 +24,9 @@
 import android.view.View;
 import android.widget.LinearLayout;
 
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -92,7 +92,7 @@
     public final void close(boolean animate) {
         animate &= !Utilities.isPowerSaverOn(getContext());
         handleClose(animate);
-        Launcher.getLauncher(getContext()).getUserEventDispatcher()
+        BaseActivity.fromContext(getContext()).getUserEventDispatcher()
                 .resetElapsedContainerMillis("container closed");
     }
 
@@ -120,8 +120,8 @@
     }
 
     protected static <T extends AbstractFloatingView> T getOpenView(
-            Launcher launcher, @FloatingViewType int type) {
-        DragLayer dragLayer = launcher.getDragLayer();
+            BaseDraggingActivity activity, @FloatingViewType int type) {
+        BaseDragLayer dragLayer = activity.getDragLayer();
         // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
         // and will be one of the last views.
         for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
@@ -136,16 +136,17 @@
         return null;
     }
 
-    public static void closeOpenContainer(Launcher launcher, @FloatingViewType int type) {
-        AbstractFloatingView view = getOpenView(launcher, type);
+    public static void closeOpenContainer(BaseDraggingActivity activity,
+            @FloatingViewType int type) {
+        AbstractFloatingView view = getOpenView(activity, type);
         if (view != null) {
             view.close(true);
         }
     }
 
-    public static void closeOpenViews(Launcher launcher, boolean animate,
+    public static void closeOpenViews(BaseDraggingActivity activity, boolean animate,
             @FloatingViewType int type) {
-        DragLayer dragLayer = launcher.getDragLayer();
+        BaseDragLayer dragLayer = activity.getDragLayer();
         // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
         // and will be one of the last views.
         for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
@@ -159,16 +160,16 @@
         }
     }
 
-    public static void closeAllOpenViews(Launcher launcher, boolean animate) {
-        closeOpenViews(launcher, animate, TYPE_ALL);
-        launcher.finishAutoCancelActionMode();
+    public static void closeAllOpenViews(BaseDraggingActivity activity, boolean animate) {
+        closeOpenViews(activity, animate, TYPE_ALL);
+        activity.finishAutoCancelActionMode();
     }
 
-    public static void closeAllOpenViews(Launcher launcher) {
-        closeAllOpenViews(launcher, true);
+    public static void closeAllOpenViews(BaseDraggingActivity activity) {
+        closeAllOpenViews(activity, true);
     }
 
-    public static AbstractFloatingView getTopOpenView(Launcher launcher) {
-        return getOpenView(launcher, TYPE_ALL);
+    public static AbstractFloatingView getTopOpenView(BaseDraggingActivity activity) {
+        return getOpenView(activity, TYPE_ALL);
     }
 }
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 12db3b6..02d70c4 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -20,6 +20,8 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
+import android.graphics.Point;
+import android.view.Display;
 import android.view.View.AccessibilityDelegate;
 
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
@@ -96,10 +98,26 @@
         mDPChangeListeners.add(listener);
     }
 
+    public void removeOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) {
+        mDPChangeListeners.remove(listener);
+    }
+
     protected void dispatchDeviceProfileChanged() {
-        int count = mDPChangeListeners.size();
-        for (int i = 0; i < count; i++) {
+        for (int i = mDPChangeListeners.size() - 1; i >= 0; i--) {
             mDPChangeListeners.get(i).onDeviceProfileChanged(mDeviceProfile);
         }
     }
+
+    /**
+     * Sets the device profile, adjusting it accordingly in case of multi-window
+     */
+    protected void setDeviceProfile(DeviceProfile dp) {
+        mDeviceProfile = dp;
+        if (isInMultiWindowModeCompat()) {
+            Display display = getWindowManager().getDefaultDisplay();
+            Point mwSize = new Point();
+            display.getSize(mwSize);
+            mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
new file mode 100644
index 0000000..458f7b2
--- /dev/null
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -0,0 +1,217 @@
+/*
+ * 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 android.app.ActivityOptions;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.StrictMode;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.ActionMode;
+import android.view.View;
+import android.widget.Toast;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.badge.BadgeInfo;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.views.BaseDragLayer;
+
+/**
+ * Extension of BaseActivity allowing support for drag-n-drop
+ */
+public abstract class BaseDraggingActivity extends BaseActivity {
+
+    private static final String TAG = "BaseDraggingActivity";
+
+    // The Intent extra that defines whether to ignore the launch animation
+    private static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
+            "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
+
+    // When starting an action mode, setting this tag will cause the action mode to be cancelled
+    // automatically when user interacts with the launcher.
+    public static final Object AUTO_CANCEL_ACTION_MODE = new Object();
+
+    private ActionMode mCurrentActionMode;
+    protected boolean mIsSafeModeEnabled;
+
+    private OnStartCallback mOnStartCallback;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mIsSafeModeEnabled = getPackageManager().isSafeMode();
+    }
+
+    @Override
+    public void onActionModeStarted(ActionMode mode) {
+        super.onActionModeStarted(mode);
+        mCurrentActionMode = mode;
+    }
+
+    @Override
+    public void onActionModeFinished(ActionMode mode) {
+        super.onActionModeFinished(mode);
+        mCurrentActionMode = null;
+    }
+
+    public boolean finishAutoCancelActionMode() {
+        if (mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag()) {
+            mCurrentActionMode.finish();
+            return true;
+        }
+        return false;
+    }
+
+    public abstract BaseDragLayer getDragLayer();
+
+    public abstract <T extends View> T getOverviewPanel();
+
+    public abstract View getRootView();
+
+    public abstract BadgeInfo getBadgeInfoForItem(ItemInfo info);
+
+    public abstract void invalidateParent(ItemInfo info);
+
+    public static BaseDraggingActivity fromContext(Context context) {
+        if (context instanceof BaseDraggingActivity) {
+            return (BaseDraggingActivity) context;
+        }
+        return ((BaseDraggingActivity) ((ContextWrapper) context).getBaseContext());
+    }
+
+    public Rect getViewBounds(View v) {
+        int[] pos = new int[2];
+        v.getLocationOnScreen(pos);
+        return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight());
+    }
+
+    public final Bundle getActivityLaunchOptionsAsBundle(View v, boolean useDefaultLaunchOptions) {
+        ActivityOptions activityOptions = getActivityLaunchOptions(v, useDefaultLaunchOptions);
+        return activityOptions == null ? null : activityOptions.toBundle();
+    }
+
+    public abstract ActivityOptions getActivityLaunchOptions(
+            View v, boolean useDefaultLaunchOptions);
+
+    public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
+        if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
+            Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
+            return false;
+        }
+
+        // Only launch using the new animation if the shortcut has not opted out (this is a
+        // private contract between launcher and may be ignored in the future).
+        boolean useLaunchAnimation = (v != null) &&
+                !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
+        Bundle optsBundle = useLaunchAnimation
+                ? getActivityLaunchOptionsAsBundle(v, isInMultiWindowModeCompat())
+                : null;
+
+        UserHandle user = item == null ? null : item.user;
+
+        // Prepare intent
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        if (v != null) {
+            intent.setSourceBounds(getViewBounds(v));
+        }
+        try {
+            boolean isShortcut = Utilities.ATLEAST_MARSHMALLOW
+                    && (item instanceof ShortcutInfo)
+                    && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
+                    || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
+                    && !((ShortcutInfo) item).isPromise();
+            if (isShortcut) {
+                // Shortcuts need some special checks due to legacy reasons.
+                startShortcutIntentSafely(intent, optsBundle, item);
+            } else if (user == null || user.equals(Process.myUserHandle())) {
+                // Could be launching some bookkeeping activity
+                startActivity(intent, optsBundle);
+            } else {
+                LauncherAppsCompat.getInstance(this).startActivityForProfile(
+                        intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
+            }
+            getUserEventDispatcher().logAppLaunch(v, intent);
+            return true;
+        } catch (ActivityNotFoundException|SecurityException e) {
+            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+            Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e);
+        }
+        return false;
+    }
+
+    private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) {
+        try {
+            StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
+            try {
+                // Temporarily disable deathPenalty on all default checks. For eg, shortcuts
+                // containing file Uri's would cause a crash as penaltyDeathOnFileUriExposure
+                // is enabled by default on NYC.
+                StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll()
+                        .penaltyLog().build());
+
+                if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                    String id = ((ShortcutInfo) info).getDeepShortcutId();
+                    String packageName = intent.getPackage();
+                    DeepShortcutManager.getInstance(this).startShortcut(
+                            packageName, id, intent.getSourceBounds(), optsBundle, info.user);
+                } else {
+                    // Could be launching some bookkeeping activity
+                    startActivity(intent, optsBundle);
+                }
+            } finally {
+                StrictMode.setVmPolicy(oldPolicy);
+            }
+        } catch (SecurityException e) {
+            if (!onErrorStartingShortcut(intent, info)) {
+                throw e;
+            }
+        }
+    }
+
+    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/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 8b6d9f8..41bfcb7 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -28,6 +28,7 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.support.v4.graphics.ColorUtils;
+import android.text.TextUtils.TruncateAt;
 import android.util.AttributeSet;
 import android.util.Property;
 import android.util.TypedValue;
@@ -44,7 +45,6 @@
 import com.android.launcher3.badge.BadgeInfo;
 import com.android.launcher3.badge.BadgeRenderer;
 import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.folder.FolderIconPreviewVerifier;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.graphics.PreloadIconDrawable;
@@ -65,7 +65,7 @@
 
     private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed};
 
-    private final Launcher mLauncher;
+    private final BaseDraggingActivity mActivity;
     private Drawable mIcon;
     private final boolean mCenterVertically;
 
@@ -133,8 +133,8 @@
 
     public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-        mLauncher = Launcher.getLauncher(context);
-        DeviceProfile grid = mLauncher.getDeviceProfile();
+        mActivity = BaseDraggingActivity.fromContext(context);
+        DeviceProfile grid = mActivity.getDeviceProfile();
         mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
 
         TypedArray a = context.obtainStyledAttributes(attrs,
@@ -164,10 +164,18 @@
         mLongPressHelper = new CheckLongPressHelper(this);
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
-        setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
+        setEllipsize(TruncateAt.END);
+        setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
 
     }
 
+    @Override
+    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+        // Disable marques when not focused to that, so that updating text does not cause relayout.
+        setEllipsize(focused ? TruncateAt.MARQUEE : TruncateAt.END);
+        super.onFocusChanged(focused, direction, previouslyFocusedRect);
+    }
+
     /**
      * Resets the view so it can be recycled.
      */
@@ -420,7 +428,7 @@
         }
     }
 
-    private void setTextAlpha(int alpha) {
+    public void setTextAlpha(int alpha) {
         super.setTextColor(ColorUtils.setAlphaComponent(mTextColor, alpha));
     }
 
@@ -493,10 +501,10 @@
     public void applyBadgeState(ItemInfo itemInfo, boolean animate) {
         if (mIcon instanceof FastBitmapDrawable) {
             boolean wasBadged = mBadgeInfo != null;
-            mBadgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo);
+            mBadgeInfo = mActivity.getBadgeInfoForItem(itemInfo);
             boolean isBadged = mBadgeInfo != null;
             float newBadgeScale = isBadged ? 1f : 0;
-            mBadgeRenderer = mLauncher.getDeviceProfile().mBadgeRenderer;
+            mBadgeRenderer = mActivity.getDeviceProfile().mBadgeRenderer;
             if (wasBadged || isBadged) {
                 // Animate when a badge is first added or when it is removed.
                 if (animate && (wasBadged ^ isBadged) && isShown()) {
@@ -522,31 +530,30 @@
      * Sets the icon for this view based on the layout direction.
      */
     private void setIcon(Drawable icon) {
-        mIcon = icon;
-        mIcon.setBounds(0, 0, mIconSize, mIconSize);
         if (mIsIconVisible) {
-            applyCompoundDrawables(mIcon);
+            applyCompoundDrawables(icon);
         }
+        mIcon = icon;
     }
 
     public void setIconVisible(boolean visible) {
         mIsIconVisible = visible;
-        mDisableRelayout = true;
-        Drawable icon = mIcon;
-        if (!visible) {
-            icon = new ColorDrawable(Color.TRANSPARENT);
-            icon.setBounds(0, 0, mIconSize, mIconSize);
-        }
+        Drawable icon = visible ? mIcon : new ColorDrawable(Color.TRANSPARENT);
         applyCompoundDrawables(icon);
-        mDisableRelayout = false;
     }
 
     protected void applyCompoundDrawables(Drawable icon) {
+        // If we had already set an icon before, disable relayout as the icon size is the
+        // same as before.
+        mDisableRelayout = mIcon != null;
+
+        icon.setBounds(0, 0, mIconSize, mIconSize);
         if (mLayoutHorizontal) {
             setCompoundDrawablesRelative(icon, null, null, null);
         } else {
             setCompoundDrawables(null, icon, null, null);
         }
+        mDisableRelayout = false;
     }
 
     @Override
@@ -572,15 +579,7 @@
                 applyFromApplicationInfo((AppInfo) info);
             } else if (info instanceof ShortcutInfo) {
                 applyFromShortcutInfo((ShortcutInfo) info);
-                FolderIconPreviewVerifier verifier =
-                        new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv);
-                if (verifier.isItemInPreview(info.rank) && (info.container >= 0)) {
-                    View folderIcon =
-                            mLauncher.getWorkspace().getHomescreenIconByItemId(info.container);
-                    if (folderIcon != null) {
-                        folderIcon.invalidate();
-                    }
-                }
+                mActivity.invalidateParent(info);
             } else if (info instanceof PackageItemInfo) {
                 applyFromPackageItemInfo((PackageItemInfo) info);
             }
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 734aec3..7979082 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -292,7 +292,7 @@
             ViewCompat.setAccessibilityDelegate(this, null);
             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
             getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
-            setOnClickListener(mLauncher);
+            setOnClickListener(null);
         } else {
             if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
                     !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index ea52324..13971ad 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -515,12 +515,6 @@
         }
     }
 
-    public boolean shouldIgnoreLongPressToOverview(float touchX) {
-        boolean touchedLhsEdge = mInsets.left == 0 && touchX < edgeMarginPx;
-        boolean touchedRhsEdge = mInsets.right == 0 && touchX > (widthPx - edgeMarginPx);
-        return !isMultiWindowMode && (touchedLhsEdge || touchedRhsEdge);
-    }
-
     private static Context getContext(Context c, int orientation) {
         Configuration context = new Configuration(c.getResources().getConfiguration());
         context.orientation = orientation;
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index a3fe89a..dec6cb4 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -16,10 +16,10 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.AlphaUpdateListener.updateVisibility;
 import static com.android.launcher3.ButtonDropTarget.TOOLTIP_DEFAULT;
 import static com.android.launcher3.ButtonDropTarget.TOOLTIP_LEFT;
 import static com.android.launcher3.ButtonDropTarget.TOOLTIP_RIGHT;
+import static com.android.launcher3.anim.AlphaUpdateListener.updateVisibility;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 
 import android.animation.TimeInterpolator;
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 5354edf..3873a81 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -58,7 +58,7 @@
     private static final ColorMatrix sTempFilterMatrix = new ColorMatrix();
 
     protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
-    private final Bitmap mBitmap;
+    protected Bitmap mBitmap;
     protected final int mIconColor;
 
     private boolean mIsPressed;
@@ -324,10 +324,9 @@
         return new MyConstantState(mBitmap, mIconColor);
     }
 
-    private static class MyConstantState extends ConstantState {
-        private final Bitmap mBitmap;
-        private final int mIconColor;
-
+    protected static class MyConstantState extends ConstantState {
+        protected final Bitmap mBitmap;
+        protected final int mIconColor;
 
         public MyConstantState(Bitmap bitmap, int color) {
             mBitmap = bitmap;
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 03043f2..211a756 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -63,14 +63,6 @@
         return mContent;
     }
 
-    /**
-     * Registers the specified listener on the cell layout of the hotseat.
-     */
-    @Override
-    public void setOnLongClickListener(OnLongClickListener l) {
-        mContent.setOnLongClickListener(l);
-    }
-
     /* Get the orientation invariant order of the item in the hotseat for persistence. */
     int getOrderInHotseat(int x, int y) {
         return mHasVerticalHotseat ? (mContent.getCountY() - y - 1) : x;
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index f4ae62a..ab73074 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -641,11 +641,8 @@
                     // Load the full res icon for the application, but if useLowResIcon is set, then
                     // only keep the low resolution icon instead of the larger full-sized icon
                     BitmapInfo iconInfo = li.createBadgedIconBitmap(
-                            appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion);
-                    if (mInstantAppResolver.isInstantApp(appInfo)) {
-                        li.badgeWithDrawable(iconInfo.icon,
-                                mContext.getDrawable(R.drawable.ic_instant_app_badge));
-                    }
+                            appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion,
+                            mInstantAppResolver.isInstantApp(appInfo));
                     li.recycle();
 
                     Bitmap lowResIcon =  generateLowResIcon(iconInfo.icon);
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index e460911..f63cce5 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -22,6 +22,7 @@
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.Point;
+import android.support.annotation.VisibleForTesting;
 import android.util.DisplayMetrics;
 import android.util.Xml;
 import android.view.Display;
@@ -88,17 +89,18 @@
 
     public Point defaultWallpaperSize;
 
+    @VisibleForTesting
     public InvariantDeviceProfile() {
     }
 
-    public InvariantDeviceProfile(InvariantDeviceProfile p) {
+    private InvariantDeviceProfile(InvariantDeviceProfile p) {
         this(p.name, p.minWidthDps, p.minHeightDps, p.numRows, p.numColumns,
                 p.numFolderRows, p.numFolderColumns,
                 p.iconSize, p.landscapeIconSize, p.iconTextSize, p.numHotseatIcons,
                 p.defaultLayoutId, p.demoModeLayoutId);
     }
 
-    InvariantDeviceProfile(String n, float w, float h, int r, int c, int fr, int fc,
+    private InvariantDeviceProfile(String n, float w, float h, int r, int c, int fr, int fc,
             float is, float lis, float its, int hs, int dlId, int dmlId) {
         name = n;
         minWidthDps = w;
@@ -116,7 +118,7 @@
     }
 
     @TargetApi(23)
-    InvariantDeviceProfile(Context context) {
+    public InvariantDeviceProfile(Context context) {
         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         Display display = wm.getDefaultDisplay();
         DisplayMetrics dm = new DisplayMetrics();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 375deb7..e779b5e 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -25,13 +25,11 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 
-import android.Manifest;
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
-import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
 import android.appwidget.AppWidgetHostView;
@@ -48,9 +46,6 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
@@ -63,32 +58,25 @@
 import android.text.method.TextKeyListener;
 import android.util.Log;
 import android.util.SparseArray;
-import android.view.ActionMode;
-import android.view.Display;
-import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.KeyboardShortcutGroup;
 import android.view.KeyboardShortcutInfo;
 import android.view.LayoutInflater;
 import android.view.Menu;
-import android.view.MotionEvent;
 import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
 import android.widget.Toast;
 
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.badge.BadgeInfo;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.LauncherAppsCompatVO;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
@@ -96,6 +84,7 @@
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.dynamicui.WallpaperColorInfo;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.FolderIconPreviewVerifier;
 import com.android.launcher3.keyboard.CustomActionsPopup;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.logging.FileLog;
@@ -109,7 +98,6 @@
 import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.uioverrides.UiFactory;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -126,6 +114,7 @@
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.views.OptionsPopupView;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -147,10 +136,8 @@
 /**
  * Default launcher application.
  */
-public class Launcher extends BaseActivity
-        implements LauncherExterns, OnClickListener, OnLongClickListener,
-                   LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener,
-                   WallpaperColorInfo.OnThemeChangeListener {
+public class Launcher extends BaseDraggingActivity implements LauncherExterns, LauncherModel.Callbacks,
+        LauncherProviderChangeListener, WallpaperColorInfo.OnThemeChangeListener {
     public static final String TAG = "Launcher";
     static final boolean LOGD = false;
 
@@ -176,10 +163,6 @@
      */
     protected static final int REQUEST_LAST = 100;
 
-    // The Intent extra that defines whether to ignore the launch animation
-    static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
-            "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
-
     // Type: int
     private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
     // Type: int
@@ -191,14 +174,8 @@
     // Type: SparseArray<Parcelable>
     private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
 
-    // When starting an action mode, setting this tag will cause the action mode to be cancelled
-    // automatically when user interacts with the launcher.
-    public static final Object AUTO_CANCEL_ACTION_MODE = new Object();
-
     private LauncherStateManager mStateManager;
 
-    private boolean mIsSafeModeEnabled;
-
     private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
 
     // How long to wait before the new-shortcut animation automatically pans the workspace
@@ -228,11 +205,10 @@
     AllAppsTransitionController mAllAppsController;
 
     // UI and state for the overview panel
-    private ViewGroup mOverviewPanel;
+    private View mOverviewPanel;
 
     @Thunk boolean mWorkspaceLoading = true;
 
-    private OnStartCallback mOnStartCallback;
     private OnResumeCallback mOnResumeCallback;
 
     private ViewOnDrawExecutor mPendingExecutor;
@@ -261,13 +237,10 @@
      */
     private PendingRequestArgs mPendingRequestArgs;
 
-    private final PointF mLastDispatchTouchEvent = new PointF();
-
     public ViewGroupFocusHelper mFocusHandler;
     private boolean mAppLaunchSuccess;
 
     private RotationHelper mRotationHelper;
-    private ActionMode mCurrentActionMode;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -300,7 +273,6 @@
         initDeviceProfile(app.getInvariantDeviceProfile());
 
         mSharedPrefs = Utilities.getPrefs(this);
-        mIsSafeModeEnabled = getPackageManager().isSafeMode();
         mIconCache = app.getIconCache();
         mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);
 
@@ -392,6 +364,9 @@
             getRootView().dispatchInsets();
             getStateManager().reapplyState();
 
+            // Recreate touch controllers
+            mDragLayer.setup(mDragController);
+
             // TODO: We can probably avoid rebind when only screen size changed.
             rebindModel();
         }
@@ -411,13 +386,7 @@
 
     private void initDeviceProfile(InvariantDeviceProfile idp) {
         // Load configuration-specific DeviceProfile
-        mDeviceProfile = idp.getDeviceProfile(this);
-        if (isInMultiWindowModeCompat()) {
-            Display display = getWindowManager().getDefaultDisplay();
-            Point mwSize = new Point();
-            display.getSize(mwSize);
-            mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
-        }
+        setDeviceProfile(idp.getDeviceProfile(this));
         mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout(), true);
     }
 
@@ -434,10 +403,6 @@
         return mStateManager;
     }
 
-    public LauncherAppTransitionManager getAppTransitionManager() {
-        return mAppTransitionManager;
-    }
-
     protected void overrideTheme(boolean isDark, boolean supportsDarkText) {
         if (isDark) {
             setTheme(R.style.LauncherThemeDark);
@@ -500,6 +465,22 @@
         return mPopupDataProvider;
     }
 
+    @Override
+    public BadgeInfo getBadgeInfoForItem(ItemInfo info) {
+        return mPopupDataProvider.getBadgeInfoForItem(info);
+    }
+
+    @Override
+    public void invalidateParent(ItemInfo info) {
+        FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(getDeviceProfile().inv);
+        if (verifier.isItemInPreview(info.rank) && (info.container >= 0)) {
+            View folderIcon = getWorkspace().getHomescreenIconByItemId(info.container);
+            if (folderIcon != null) {
+                folderIcon.invalidate();
+            }
+        }
+    }
+
     /**
      * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
      * a configuration step, this allows the proper animations to run after other transitions.
@@ -784,10 +765,6 @@
         super.onStart();
         FirstFrameAnimatorHelper.setIsVisible(true);
 
-        if (mOnStartCallback != null) {
-            mOnStartCallback.onLauncherStart(this);
-            mOnStartCallback = null;
-        }
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onStart();
         }
@@ -952,23 +929,15 @@
         mWorkspace = mDragLayer.findViewById(R.id.workspace);
         mWorkspace.initParentViews(mDragLayer);
         mOverviewPanel = findViewById(R.id.overview_panel);
+        mHotseat = findViewById(R.id.hotseat);
 
         mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
 
         // Setup the drag layer
-        mDragLayer.setup(this, mDragController);
+        mDragLayer.setup(mDragController);
 
-        // Setup the hotseat
-        mHotseat = (Hotseat) findViewById(R.id.hotseat);
-        if (mHotseat != null) {
-            mHotseat.setOnLongClickListener(this);
-        }
-
-        // Setup the workspace
-        mWorkspace.setHapticFeedbackEnabled(false);
-        mWorkspace.setOnLongClickListener(this);
         mWorkspace.setup(mDragController);
         // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
         // default state, otherwise we will update to the wrong offsets in RTL
@@ -986,7 +955,7 @@
         mDragController.setMoveTarget(mWorkspace);
         mDropTargetBar.setup(mDragController);
 
-        mAllAppsController.setupViews(mAppsView, mHotseat);
+        mAllAppsController.setupViews(mAppsView);
     }
 
     /**
@@ -1202,10 +1171,12 @@
         return mAllAppsController;
     }
 
+    @Override
     public LauncherRootView getRootView() {
         return (LauncherRootView) mLauncherView;
     }
 
+    @Override
     public DragLayer getDragLayer() {
         return mDragLayer;
     }
@@ -1222,7 +1193,7 @@
         return mHotseat;
     }
 
-    public <T extends ViewGroup> T getOverviewPanel() {
+    public <T extends View> T getOverviewPanel() {
         return (T) mOverviewPanel;
     }
 
@@ -1282,11 +1253,15 @@
                 // In all these cases, only animate if we're already on home
                 AbstractFloatingView.closeAllOpenViews(this, isStarted());
 
-                mStateManager.goToState(NORMAL);
+                if (!isInState(NORMAL)) {
+                    // Only change state, if not already the same. This prevents cancelling any
+                    // animations running as part of resume
+                    mStateManager.goToState(NORMAL);
+                }
 
                 // Reset the apps view
                 if (!alreadyOnHome && mAppsView != null) {
-                    mAppsView.reset();
+                    mAppsView.reset(isStarted() /* animate */);
                 }
 
                 if (shouldMoveToDefaultScreen && !mWorkspace.isTouchActive()) {
@@ -1659,51 +1634,6 @@
     }
 
     /**
-     * Launches the intent referred by the clicked shortcut.
-     *
-     * @param v The view representing the clicked shortcut.
-     */
-    @Override
-    public void onClick(View v) {
-        // Make sure that rogue clicks don't get through while allapps is launching, or after the
-        // view has detached (it's possible for this to happen if the view is removed mid touch).
-        if (v.getWindowToken() == null) {
-            return;
-        }
-
-        if (!mWorkspace.isFinishedSwitchingState()) {
-            return;
-        }
-
-        if (v instanceof Workspace) {
-            if (isInState(OVERVIEW)) {
-                getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Touch.TAP,
-                        LauncherLogProto.Action.Direction.NONE,
-                        LauncherLogProto.ContainerType.OVERVIEW, mWorkspace.getCurrentPage());
-                mStateManager.goToState(NORMAL);
-            }
-            return;
-        }
-
-        if (v instanceof CellLayout) {
-            if (isInState(OVERVIEW)) {
-                int page = mWorkspace.indexOfChild(v);
-                getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Touch.TAP,
-                        LauncherLogProto.Action.Direction.NONE,
-                        LauncherLogProto.ContainerType.OVERVIEW, page);
-                mWorkspace.snapToPageFromOverView(page);
-                mStateManager.goToState(NORMAL);
-            }
-            return;
-        }
-    }
-
-    @SuppressLint("ClickableViewAccessibility")
-    public boolean onTouch(View v, MotionEvent event) {
-        return false;
-    }
-
-    /**
      * Event handler for the wallpaper picker button that appears after a long press
      * on the home screen.
      */
@@ -1743,179 +1673,49 @@
         }
     }
 
-    private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) {
-        try {
-            StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
-            try {
-                // Temporarily disable deathPenalty on all default checks. For eg, shortcuts
-                // containing file Uri's would cause a crash as penaltyDeathOnFileUriExposure
-                // is enabled by default on NYC.
-                StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll()
-                        .penaltyLog().build());
-
-                if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-                    String id = ((ShortcutInfo) info).getDeepShortcutId();
-                    String packageName = intent.getPackage();
-                    DeepShortcutManager.getInstance(this).startShortcut(
-                            packageName, id, intent.getSourceBounds(), optsBundle, info.user);
-                } else {
-                    // Could be launching some bookkeeping activity
-                    startActivity(intent, optsBundle);
-                }
-            } finally {
-                StrictMode.setVmPolicy(oldPolicy);
-            }
-        } catch (SecurityException e) {
-            // Due to legacy reasons, direct call shortcuts require Launchers to have the
-            // corresponding permission. Show the appropriate permission prompt if that
-            // is the case.
-            if (intent.getComponent() == null
-                    && Intent.ACTION_CALL.equals(intent.getAction())
-                    && checkSelfPermission(Manifest.permission.CALL_PHONE) !=
-                    PackageManager.PERMISSION_GRANTED) {
-
-                setWaitingForResult(PendingRequestArgs
-                        .forIntent(REQUEST_PERMISSION_CALL_PHONE, intent, info));
-                requestPermissions(new String[]{Manifest.permission.CALL_PHONE},
-                        REQUEST_PERMISSION_CALL_PHONE);
-            } else {
-                // No idea why this was thrown.
-                throw e;
-            }
-        }
-    }
-
-    public Bundle getActivityLaunchOptionsAsBundle(View v, boolean useDefaultLaunchOptions) {
-        ActivityOptions activityOptions = getActivityLaunchOptions(v, useDefaultLaunchOptions);
-        return activityOptions == null ? null : activityOptions.toBundle();
-    }
-
     @TargetApi(Build.VERSION_CODES.M)
+    @Override
     public ActivityOptions getActivityLaunchOptions(View v, boolean useDefaultLaunchOptions) {
         return useDefaultLaunchOptions
                 ? mAppTransitionManager.getDefaultActivityLaunchOptions(this, v)
                 : mAppTransitionManager.getActivityLaunchOptions(this, v);
     }
 
-    public Rect getViewBounds(View v) {
-        int[] pos = new int[2];
-        v.getLocationOnScreen(pos);
-        return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight());
+    @TargetApi(Build.VERSION_CODES.M)
+    @Override
+    protected boolean onErrorStartingShortcut(Intent intent, ItemInfo info) {
+        // Due to legacy reasons, direct call shortcuts require Launchers to have the
+        // corresponding permission. Show the appropriate permission prompt if that
+        // is the case.
+        if (intent.getComponent() == null
+                && Intent.ACTION_CALL.equals(intent.getAction())
+                && checkSelfPermission(android.Manifest.permission.CALL_PHONE) !=
+                PackageManager.PERMISSION_GRANTED) {
+
+            setWaitingForResult(PendingRequestArgs
+                    .forIntent(REQUEST_PERMISSION_CALL_PHONE, intent, info));
+            requestPermissions(new String[]{android.Manifest.permission.CALL_PHONE},
+                    REQUEST_PERMISSION_CALL_PHONE);
+            return true;
+        } else {
+            return false;
+        }
     }
 
     public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
-        mAppLaunchSuccess = false;
-        if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
-            Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
-            return mAppLaunchSuccess;
-        }
-
-        // Only launch using the new animation if the shortcut has not opted out (this is a
-        // private contract between launcher and may be ignored in the future).
-        boolean useLaunchAnimation = (v != null) &&
-                !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
-        Bundle optsBundle = useLaunchAnimation
-                ? getActivityLaunchOptionsAsBundle(v, isInMultiWindowModeCompat())
-                : null;
-
-        UserHandle user = item == null ? null : item.user;
-
-        // Prepare intent
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        if (v != null) {
-            intent.setSourceBounds(getViewBounds(v));
-        }
-        try {
-            boolean isShortcut = Utilities.ATLEAST_MARSHMALLOW
-                    && (item instanceof ShortcutInfo)
-                    && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
-                    || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
-                    && !((ShortcutInfo) item).isPromise();
-            if (isShortcut) {
-                // Shortcuts need some special checks due to legacy reasons.
-                startShortcutIntentSafely(intent, optsBundle, item);
-            } else if (user == null || user.equals(Process.myUserHandle())) {
-                // Could be launching some bookkeeping activity
-                startActivity(intent, optsBundle);
-            } else {
-                LauncherAppsCompat.getInstance(this).startActivityForProfile(
-                        intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
-            }
-
-            if (v instanceof BubbleTextView) {
-                // This is set to the view that launched the activity that navigated the user away
-                // from launcher. Since there is no callback for when the activity has finished
-                // launching, enable the press state and keep this reference to reset the press
-                // state when we return to launcher.
-                BubbleTextView btv = (BubbleTextView) v;
-                btv.setStayPressed(true);
-                setOnResumeCallback(btv);
-            }
-            mAppLaunchSuccess = true;
-            getUserEventDispatcher().logAppLaunch(v, intent); // TODO for discovered apps b/35802115
-        } catch (ActivityNotFoundException|SecurityException e) {
-            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
-            Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e);
+        mAppLaunchSuccess = super.startActivitySafely(v, intent, item);
+        if (mAppLaunchSuccess && v instanceof BubbleTextView) {
+            // This is set to the view that launched the activity that navigated the user away
+            // from launcher. Since there is no callback for when the activity has finished
+            // launching, enable the press state and keep this reference to reset the press
+            // state when we return to launcher.
+            BubbleTextView btv = (BubbleTextView) v;
+            btv.setStayPressed(true);
+            setOnResumeCallback(btv);
         }
         return mAppLaunchSuccess;
     }
 
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        mLastDispatchTouchEvent.set(ev.getX(), ev.getY());
-        return super.dispatchTouchEvent(ev);
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
-        if (!isDraggingEnabled()) return false;
-        if (isWorkspaceLocked()) return false;
-        if (!isInState(NORMAL) && !isInState(OVERVIEW)) return false;
-
-        boolean ignoreLongPressToOverview =
-                mDeviceProfile.shouldIgnoreLongPressToOverview(mLastDispatchTouchEvent.x);
-
-        if (v instanceof Workspace) {
-            if (!isInState(OVERVIEW)) {
-                if (!mWorkspace.isTouchActive() && !ignoreLongPressToOverview) {
-                    getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
-                            Action.Direction.NONE, ContainerType.WORKSPACE,
-                            mWorkspace.getCurrentPage());
-                    UiFactory.onWorkspaceLongPress(this, mLastDispatchTouchEvent);
-                    mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
-                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
-                    return true;
-                } else {
-                    return false;
-                }
-            } else {
-                return false;
-            }
-        }
-
-        // The hotseat touch handling does not go through Workspace, and we always allow long press
-        // on hotseat items.
-        if (!mDragController.isDragging()) {
-            // User long pressed on empty space
-            if (mWorkspace.isPageRearrangeEnabled()) {
-                mWorkspace.startReordering(v);
-                getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
-                        Action.Direction.NONE, ContainerType.OVERVIEW);
-            } else {
-                if (ignoreLongPressToOverview) {
-                    return false;
-                }
-                getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
-                        Action.Direction.NONE, ContainerType.WORKSPACE,
-                        mWorkspace.getCurrentPage());
-                UiFactory.onWorkspaceLongPress(this, mLastDispatchTouchEvent);
-            }
-            mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
-                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
-        }
-        return true;
-    }
-
     boolean isHotseatLayout(View layout) {
         // TODO: Remove this method
         return mHotseat != null && layout != null &&
@@ -1974,10 +1774,6 @@
         mOnResumeCallback = callback;
     }
 
-    public void setOnStartCallback(OnStartCallback callback) {
-        mOnStartCallback = callback;
-    }
-
     /**
      * Implementation of the method from LauncherModel.Callbacks.
      */
@@ -2628,8 +2424,7 @@
 
                 // Setting the touch point to (-1, -1) will show the options popup in the center of
                 // the screen.
-                mLastDispatchTouchEvent.set(-1, -1);
-                UiFactory.onWorkspaceLongPress(this, mLastDispatchTouchEvent);
+                OptionsPopupView.show(this, -1, -1);
             }
             return true;
         }
@@ -2643,26 +2438,6 @@
         return ((Launcher) ((ContextWrapper) context).getBaseContext());
     }
 
-    @Override
-    public void onActionModeStarted(ActionMode mode) {
-        super.onActionModeStarted(mode);
-        mCurrentActionMode = mode;
-    }
-
-    @Override
-    public void onActionModeFinished(ActionMode mode) {
-        super.onActionModeFinished(mode);
-        mCurrentActionMode = null;
-    }
-
-    public boolean finishAutoCancelActionMode() {
-        if (mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag()) {
-            mCurrentActionMode.finish();
-            return true;
-        }
-        return false;
-    }
-
     /**
      * Callback for listening for onResume
      */
@@ -2670,12 +2445,4 @@
 
         void onLauncherResume();
     }
-
-    /**
-     * Callback for listening for onStart
-     */
-    public interface OnStartCallback {
-
-        void onLauncherStart(Launcher launcher);
-    }
 }
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
index 19fa3d4..04f9b3a 100644
--- a/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ b/src/com/android/launcher3/LauncherAppTransitionManager.java
@@ -62,13 +62,4 @@
     public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
         return getDefaultActivityLaunchOptions(launcher, v);
     }
-
-    /** Cancels the current Launcher transition animation */
-    public void finishLauncherAnimation() {
-    }
-
-    public boolean isAnimating() {
-        // We don't know when the activity options are being used.
-        return false;
-    }
 }
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index fc4de2d..b1273b6 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -29,6 +29,7 @@
     private int mRightInsetBarWidth;
 
     private View mAlignedView;
+    private WindowStateListener mWindowStateListener;
 
     public LauncherRootView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -117,4 +118,31 @@
             }
         }
     }
+
+    public void setWindowStateListener(WindowStateListener listener) {
+        mWindowStateListener = listener;
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        super.onWindowFocusChanged(hasWindowFocus);
+        if (mWindowStateListener != null) {
+            mWindowStateListener.onWindowFocusChanged(hasWindowFocus);
+        }
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        if (mWindowStateListener != null) {
+            mWindowStateListener.onWindowVisibilityChanged(visibility);
+        }
+    }
+
+    public interface WindowStateListener {
+
+        void onWindowFocusChanged(boolean hasFocus);
+
+        void onWindowVisibilityChanged(int visibility);
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 4e6bcdc..9fef64a 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -40,6 +40,16 @@
  */
 public class LauncherState {
 
+
+    /**
+     * Set of elements indicating various workspace elements which change visibility across states
+     * Note that workspace is not included here as in that case, we animate individual pages
+     */
+    public static final int NONE = 0;
+    public static final int HOTSEAT = 1 << 0;
+    public static final int ALL_APPS_HEADER = 1 << 1;
+    public static final int ALL_APPS_CONTENT = 1 << 2;
+
     protected static final int FLAG_SHOW_SCRIM = 1 << 0;
     protected static final int FLAG_MULTI_PAGE = 1 << 1;
     protected static final int FLAG_DISABLE_ACCESSIBILITY = 1 << 2;
@@ -51,7 +61,6 @@
     protected static final int FLAG_DISABLE_INTERACTION = 1 << 8;
     protected static final int FLAG_OVERVIEW_UI = 1 << 9;
 
-
     protected static final PageAlphaProvider DEFAULT_ALPHA_PROVIDER =
             new PageAlphaProvider(ACCEL_2) {
                 @Override
@@ -68,13 +77,13 @@
     public static final LauncherState NORMAL = new LauncherState(0, ContainerType.WORKSPACE,
             0, FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED);
 
-    public static final LauncherState ALL_APPS = new AllAppsState(1);
-
-    public static final LauncherState SPRING_LOADED = new SpringLoadedState(2);
-
-    public static final LauncherState OVERVIEW = new OverviewState(3);
-
-    public static final LauncherState FAST_OVERVIEW = new FastOverviewState(4);
+    /**
+     * Various Launcher states arranged in the increasing order of UI layers
+     */
+    public static final LauncherState SPRING_LOADED = new SpringLoadedState(1);
+    public static final LauncherState OVERVIEW = new OverviewState(2);
+    public static final LauncherState FAST_OVERVIEW = new FastOverviewState(3);
+    public static final LauncherState ALL_APPS = new AllAppsState(4);
 
     public final int ordinal;
 
@@ -161,8 +170,8 @@
         return new float[] {1, 0, 0};
     }
 
-    public float getHoseatAlpha(Launcher launcher) {
-        return 1f;
+    public float getOverviewTranslationX(Launcher launcher) {
+        return launcher.getDragLayer().getMeasuredWidth();
     }
 
     public void onStateEnabled(Launcher launcher) {
@@ -175,6 +184,10 @@
         return launcher.getWorkspace();
     }
 
+    public int getVisibleElements(Launcher launcher) {
+        return HOTSEAT;
+    }
+
     /**
      * Fraction shift in the vertical translation UI and related properties
      *
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index d5813f3..7d50a52 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -28,8 +29,12 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.anim.PropertySetter.AnimatedPropertySetter;
 import com.android.launcher3.uioverrides.UiFactory;
 
+import java.util.ArrayList;
+
 /**
  * TODO: figure out what kind of tests we can write for this
  *
@@ -78,6 +83,7 @@
     private final AnimationConfig mConfig = new AnimationConfig();
     private final Handler mUiHandler;
     private final Launcher mLauncher;
+    private final ArrayList<StateListener> mListeners = new ArrayList<>();
 
     private StateHandler[] mStateHandlers;
     private LauncherState mState = NORMAL;
@@ -87,8 +93,6 @@
 
     private LauncherState mRestState;
 
-    private StateListener mStateListener;
-
     public LauncherStateManager(Launcher l) {
         mUiHandler = new Handler(Looper.getMainLooper());
         mLauncher = l;
@@ -105,8 +109,12 @@
         return mStateHandlers;
     }
 
-    public void setStateListener(StateListener stateListener) {
-        mStateListener = stateListener;
+    public void addStateListener(StateListener listener) {
+        mListeners.add(listener);
+    }
+
+    public void removeStateListener(StateListener listener) {
+        mListeners.remove(listener);
     }
 
     /**
@@ -157,11 +165,6 @@
     }
 
     private void goToState(LauncherState state, boolean animated, long delay,
-            Runnable onCompleteRunnable) {
-        goToState(state, animated, delay, -1, onCompleteRunnable);
-    }
-
-    public void goToState(LauncherState state, boolean animated, long delay, long overrideDuration,
             final Runnable onCompleteRunnable) {
         if (mLauncher.isInState(state)) {
             if (mConfig.mCurrentAnimation == null) {
@@ -188,13 +191,13 @@
         mConfig.reset();
 
         if (!animated) {
-            preOnStateTransitionStart();
             onStateTransitionStart(state);
             for (StateHandler handler : getStateHandlers()) {
                 handler.setState(state);
             }
-            if (mStateListener != null) {
-                mStateListener.onStateSetImmediately(state);
+
+            for (int i = mListeners.size() - 1; i >= 0; i--) {
+                mListeners.get(i).onStateSetImmediately(state);
             }
             onStateTransitionEnd(state);
 
@@ -208,9 +211,6 @@
         // Since state NORMAL can be reached from multiple states, just assume that the
         // transition plays in reverse and use the same duration as previous state.
         mConfig.duration = state == NORMAL ? mState.transitionDuration : state.transitionDuration;
-        if (overrideDuration > -1) {
-            mConfig.duration = overrideDuration;
-        }
 
         AnimatorSet animation = createAnimationToNewWorkspaceInternal(
                 state, new AnimatorSetBuilder(), onCompleteRunnable);
@@ -245,7 +245,6 @@
 
     protected AnimatorSet createAnimationToNewWorkspaceInternal(final LauncherState state,
             AnimatorSetBuilder builder, final Runnable onCompleteRunnable) {
-        preOnStateTransitionStart();
 
         for (StateHandler handler : getStateHandlers()) {
             builder.startTag(handler);
@@ -259,16 +258,16 @@
             public void onAnimationStart(Animator animation) {
                 // Change the internal state only when the transition actually starts
                 onStateTransitionStart(state);
-                if (mStateListener != null) {
-                    mStateListener.onStateTransitionStart(state);
+                for (int i = mListeners.size() - 1; i >= 0; i--) {
+                    mListeners.get(i).onStateTransitionStart(state);
                 }
             }
 
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
-                if (mStateListener != null) {
-                    mStateListener.onStateTransitionComplete(mState);
+                for (int i = mListeners.size() - 1; i >= 0; i--) {
+                    mListeners.get(i).onStateTransitionComplete(state);
                 }
             }
 
@@ -285,15 +284,6 @@
         return mConfig.mCurrentAnimation;
     }
 
-    private void preOnStateTransitionStart() {
-        // If we are still animating to launcher from an app,
-        // finish it and let this state animation take over.
-        LauncherAppTransitionManager transitionManager = mLauncher.getAppTransitionManager();
-        if (transitionManager != null) {
-            transitionManager.finishLauncherAnimation();
-        }
-    }
-
     private void onStateTransitionStart(LauncherState state) {
         mState.onStateDisabled(mLauncher);
         mState = state;
@@ -333,6 +323,10 @@
     }
 
     public void moveToRestState() {
+        if (mConfig.mCurrentAnimation != null && mConfig.userControlled) {
+            // The user is doing something. Lets not mess it up
+            return;
+        }
         if (mState.disableRestore) {
             goToState(getRestState());
             // Reset history
@@ -355,6 +349,15 @@
         mConfig.reset();
     }
 
+    /**
+     * Sets the animation as the current state animation, i.e., canceled when
+     * starting another animation and may block some launcher interactions while running.
+     */
+    public void setCurrentAnimation(AnimatorSet anim) {
+        cancelAnimation();
+        mConfig.setAnimation(anim);
+    }
+
     private class StartAnimRunnable implements Runnable {
 
         private final AnimatorSet mAnim;
@@ -380,12 +383,14 @@
     public static class AnimationConfig extends AnimatorListenerAdapter {
         public long duration;
         public boolean userControlled;
+        private PropertySetter mProperSetter;
 
         private AnimatorSet mCurrentAnimation;
 
         public void reset() {
             duration = 0;
             userControlled = false;
+            mProperSetter = null;
 
             if (mCurrentAnimation != null) {
                 mCurrentAnimation.setDuration(0);
@@ -394,6 +399,14 @@
             }
         }
 
+        public PropertySetter getProperSetter(AnimatorSetBuilder builder) {
+            if (mProperSetter == null) {
+                mProperSetter = duration == 0 ? NO_ANIM_PROPERTY_SETTER
+                        : new AnimatedPropertySetter(duration, builder);
+            }
+            return mProperSetter;
+        }
+
         @Override
         public void onAnimationEnd(Animator animation) {
             if (mCurrentAnimation == animation) {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index d39ec3e..9eff84b 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -19,10 +19,7 @@
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.LayoutTransition;
-import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -30,8 +27,6 @@
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.InputDevice;
@@ -48,7 +43,6 @@
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.touch.OverScroll;
 import com.android.launcher3.util.Thunk;
@@ -62,7 +56,9 @@
 public abstract class PagedView<T extends View & PageIndicator> extends ViewGroup {
     private static final String TAG = "PagedView";
     private static final boolean DEBUG = false;
+
     protected static final int INVALID_PAGE = -1;
+    protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
 
     public static final int PAGE_SNAP_ANIMATION_DURATION = 750;
     public static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
@@ -105,31 +101,21 @@
     private VelocityTracker mVelocityTracker;
     protected int mPageSpacing = 0;
 
-    private float mParentDownMotionX;
-    private float mParentDownMotionY;
     private float mDownMotionX;
     private float mDownMotionY;
-    private float mDownScrollX;
-    private float mDragViewBaselineLeft;
     private float mLastMotionX;
     private float mLastMotionXRemainder;
-    private float mLastMotionY;
     private float mTotalMotionX;
 
-    private boolean mCancelTap;
-
     private int[] mPageScrolls;
 
     protected final static int TOUCH_STATE_REST = 0;
     protected final static int TOUCH_STATE_SCROLLING = 1;
     protected final static int TOUCH_STATE_PREV_PAGE = 2;
     protected final static int TOUCH_STATE_NEXT_PAGE = 3;
-    protected final static int TOUCH_STATE_REORDERING = 4;
 
     protected int mTouchState = TOUCH_STATE_REST;
 
-    protected OnLongClickListener mLongClickListener;
-
     protected int mTouchSlop;
     private int mMaximumVelocity;
     protected boolean mAllowOverScroll = true;
@@ -153,26 +139,6 @@
     @Thunk int mPageIndicatorViewId;
     protected T mPageIndicator;
 
-    // Reordering
-    // We use the min scale to determine how much to expand the actually PagedView measured
-    // dimensions such that when we are zoomed out, the view is not clipped
-    private static int REORDERING_DROP_REPOSITION_DURATION = 200;
-    @Thunk static int REORDERING_REORDER_REPOSITION_DURATION = 300;
-    private static int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80;
-
-    @Thunk View mDragView;
-    private Runnable mSidePageHoverRunnable;
-    @Thunk int mSidePageHoverIndex = -1;
-    // This variable's scope is only for the duration of startReordering() and endReordering()
-    private boolean mReorderingStarted = false;
-    // This variable's scope is for the duration of startReordering() and after the zoomIn()
-    // animation after endReordering()
-    private boolean mIsReordering;
-    // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition
-    private static final int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2;
-    private int mPostReorderingPreZoomInRemainingAnimationCount;
-    private Runnable mPostReorderingPreZoomInRunnable;
-
     // Convenience/caching
     private static final Matrix sTmpInvMatrix = new Matrix();
     private static final float[] sTmpPoint = new float[2];
@@ -237,47 +203,6 @@
         }
     }
 
-    // Convenience methods to map points from self to parent and vice versa
-    private float[] mapPointFromViewToParent(View v, float x, float y) {
-        sTmpPoint[0] = x;
-        sTmpPoint[1] = y;
-        v.getMatrix().mapPoints(sTmpPoint);
-        sTmpPoint[0] += v.getLeft();
-        sTmpPoint[1] += v.getTop();
-        return sTmpPoint;
-    }
-    private float[] mapPointFromParentToView(View v, float x, float y) {
-        sTmpPoint[0] = x - v.getLeft();
-        sTmpPoint[1] = y - v.getTop();
-        v.getMatrix().invert(sTmpInvMatrix);
-        sTmpInvMatrix.mapPoints(sTmpPoint);
-        return sTmpPoint;
-    }
-
-    private void updateDragViewTranslationDuringDrag() {
-        if (mDragView != null) {
-            float x = (mLastMotionX - mDownMotionX) + (getScrollX() - mDownScrollX) +
-                    (mDragViewBaselineLeft - mDragView.getLeft());
-            float y = mLastMotionY - mDownMotionY;
-            mDragView.setTranslationX(x);
-            mDragView.setTranslationY(y);
-
-            if (DEBUG) Log.d(TAG, "PagedView.updateDragViewTranslationDuringDrag(): "
-                    + x + ", " + y);
-        }
-    }
-
-    @Override
-    public void setScaleX(float scaleX) {
-        super.setScaleX(scaleX);
-        if (isReordering(true)) {
-            float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
-            mLastMotionX = p[0];
-            mLastMotionY = p[1];
-            updateDragViewTranslationDuringDrag();
-        }
-    }
-
     public T getPageIndicator() {
         return mPageIndicator;
     }
@@ -380,12 +305,9 @@
     }
 
     private void updatePageIndicator() {
-        // Update the page indicator (when we aren't reordering)
         if (mPageIndicator != null) {
             mPageIndicator.setPageDescription(getPageIndicatorDescription());
-            if (!isReordering(false)) {
-                mPageIndicator.setActiveMarker(getNextPage());
-            }
+            mPageIndicator.setActiveMarker(getNextPage());
         }
     }
     protected void pageBeginTransition() {
@@ -421,21 +343,6 @@
         mWasInOverscroll = false;
     }
 
-    /**
-     * Registers the specified listener on each page contained in this workspace.
-     *
-     * @param l The listener used to respond to long clicks.
-     */
-    @Override
-    public void setOnLongClickListener(OnLongClickListener l) {
-        mLongClickListener = l;
-        final int count = getPageCount();
-        for (int i = 0; i < count; i++) {
-            getPageAt(i).setOnLongClickListener(l);
-        }
-        super.setOnLongClickListener(l);
-    }
-
     protected int getUnboundedScrollX() {
         return mUnboundedScrollX;
     }
@@ -490,14 +397,6 @@
             mOverScrollX = x;
             super.scrollTo(x, y);
         }
-
-        // Update the last motion events when scrolling
-        if (isReordering(true)) {
-            float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
-            mLastMotionX = p[0];
-            mLastMotionY = p[1];
-            updateDragViewTranslationDuringDrag();
-        }
     }
 
     private void sendScrollAccessibilityEvent() {
@@ -549,7 +448,6 @@
                 pageEndTransition();
             }
 
-            onPostReorderingAnimationCompleted();
             if (isAccessibilityEnabled(getContext())) {
                 // Notify the user when the page changes
                 announceForAccessibility(getCurrentPageDescription());
@@ -644,43 +542,13 @@
         if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
         final int childCount = getChildCount();
 
-        final int startIndex = mIsRtl ? childCount - 1 : 0;
-        final int endIndex = mIsRtl ? -1 : childCount;
-        final int delta = mIsRtl ? -1 : 1;
-
-        int verticalPadding = getPaddingTop() + getPaddingBottom();
-
-        int scrollOffsetLeft = mInsets.left + getPaddingLeft();
-        int childLeft = scrollOffsetLeft;
-
         boolean pageScrollChanged = false;
         if (mPageScrolls == null || childCount != mChildCountOnLastLayout) {
             mPageScrolls = new int[childCount];
             pageScrollChanged = true;
         }
-
-        for (int i = startIndex; i != endIndex; i += delta) {
-            final View child = getPageAt(i);
-            if (child.getVisibility() != View.GONE) {
-                int childTop = getPaddingTop() + mInsets.top;
-                childTop += (getMeasuredHeight() - mInsets.top - mInsets.bottom - verticalPadding
-                        - child.getMeasuredHeight()) / 2;
-
-                final int childWidth = child.getMeasuredWidth();
-                final int childHeight = child.getMeasuredHeight();
-
-                if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
-                child.layout(childLeft, childTop,
-                        childLeft + child.getMeasuredWidth(), childTop + childHeight);
-
-                final int pageScroll = childLeft - scrollOffsetLeft;
-                if (mPageScrolls[i] != pageScroll) {
-                    pageScrollChanged = true;
-                    mPageScrolls[i] = pageScroll;
-                }
-
-                childLeft += childWidth + mPageSpacing + getChildGap();
-            }
+        if (getPageScrolls(mPageScrolls, true, SIMPLE_SCROLL_LOGIC)) {
+            pageScrollChanged = true;
         }
 
         final LayoutTransition transition = getLayoutTransition();
@@ -716,10 +584,51 @@
             setCurrentPage(getNextPage());
         }
         mChildCountOnLastLayout = childCount;
+    }
 
-        if (isReordering(true)) {
-            updateDragViewTranslationDuringDrag();
+    /**
+     * Initializes {@code outPageScrolls} with scroll positions for view at that index. The length
+     * of {@code outPageScrolls} should be same as the the childCount
+     *
+     */
+    protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren,
+            ComputePageScrollsLogic scrollLogic) {
+        final int childCount = getChildCount();
+
+        final int startIndex = mIsRtl ? childCount - 1 : 0;
+        final int endIndex = mIsRtl ? -1 : childCount;
+        final int delta = mIsRtl ? -1 : 1;
+
+        int verticalPadding = getPaddingTop() + getPaddingBottom();
+
+        int scrollOffsetLeft = mInsets.left + getPaddingLeft();
+        int childLeft = scrollOffsetLeft;
+        boolean pageScrollChanged = false;
+
+        for (int i = startIndex; i != endIndex; i += delta) {
+            final View child = getPageAt(i);
+            if (scrollLogic.shouldIncludeView(child)) {
+                int childTop = getPaddingTop() + mInsets.top;
+                childTop += (getMeasuredHeight() - mInsets.top - mInsets.bottom - verticalPadding
+                        - child.getMeasuredHeight()) / 2;
+                final int childWidth = child.getMeasuredWidth();
+
+                if (layoutChildren) {
+                    final int childHeight = child.getMeasuredHeight();
+                    child.layout(childLeft, childTop,
+                            childLeft + child.getMeasuredWidth(), childTop + childHeight);
+                }
+
+                final int pageScroll = childLeft - scrollOffsetLeft;
+                if (outPageScrolls[i] != pageScroll) {
+                    pageScrollChanged = true;
+                    outPageScrolls[i] = pageScroll;
+                }
+
+                childLeft += childWidth + mPageSpacing + getChildGap();
+            }
         }
+        return pageScrollChanged;
     }
 
     protected int getChildGap() {
@@ -756,11 +665,13 @@
 
     @Override
     public void onViewAdded(View child) {
+        super.onViewAdded(child);
         dispatchPageCountChanged();
     }
 
     @Override
     public void onViewRemoved(View child) {
+        super.onViewRemoved(child);
         mCurrentPage = validateNewPage(mCurrentPage);
         dispatchPageCountChanged();
     }
@@ -937,12 +848,7 @@
                 // Remember location of down touch
                 mDownMotionX = x;
                 mDownMotionY = y;
-                mDownScrollX = getScrollX();
                 mLastMotionX = x;
-                mLastMotionY = y;
-                float[] p = mapPointFromViewToParent(this, x, y);
-                mParentDownMotionX = p[0];
-                mParentDownMotionY = p[1];
                 mLastMotionXRemainder = 0;
                 mTotalMotionX = 0;
                 mActivePointerId = ev.getPointerId(0);
@@ -990,6 +896,10 @@
         return mTouchState != TOUCH_STATE_REST;
     }
 
+    public boolean isHandlingTouch() {
+        return mTouchState != TOUCH_STATE_REST;
+    }
+
     protected void determineScrollingStart(MotionEvent ev) {
         determineScrollingStart(ev, 1.0f);
     }
@@ -1101,22 +1011,12 @@
         dampedOverScroll(amount);
     }
 
-    /**
-     * return true if freescroll has been enabled, false otherwise
-     */
-    protected void enableFreeScroll() {
-        enableFreeScroll(false);
-    }
 
     protected void enableFreeScroll(boolean settleOnPageInFreeScroll) {
         setEnableFreeScroll(true);
         mSettleOnPageInFreeScroll = settleOnPageInFreeScroll;
     }
 
-    protected void disableFreeScroll() {
-        setEnableFreeScroll(false);
-    }
-
     private void setEnableFreeScroll(boolean freeScroll) {
         boolean wasFreeScroll = mFreeScroll;
         mFreeScroll = freeScroll;
@@ -1134,27 +1034,6 @@
         mAllowOverScroll = enable;
     }
 
-    private int getNearestHoverOverPageIndex() {
-        if (mDragView != null) {
-            int dragX = (int) (mDragView.getLeft() + (mDragView.getMeasuredWidth() / 2)
-                    + mDragView.getTranslationX());
-            int minDistance = Integer.MAX_VALUE;
-            int minIndex = indexOfChild(mDragView);
-            int maxPageNo = getChildCount() - 1;
-            for (int i = 0; i <= maxPageNo; i++) {
-                View page = getPageAt(i);
-                int pageX = (page.getLeft() + page.getMeasuredWidth() / 2);
-                int d = Math.abs(dragX - pageX);
-                if (d < minDistance) {
-                    minIndex = i;
-                    minDistance = d;
-                }
-            }
-            return minIndex;
-        }
-        return -1;
-    }
-
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         super.onTouchEvent(ev);
@@ -1178,11 +1057,7 @@
 
             // Remember where the motion event started
             mDownMotionX = mLastMotionX = ev.getX();
-            mDownMotionY = mLastMotionY = ev.getY();
-            mDownScrollX = getScrollX();
-            float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
-            mParentDownMotionX = p[0];
-            mParentDownMotionY = p[1];
+            mDownMotionY = ev.getY();
             mLastMotionXRemainder = 0;
             mTotalMotionX = 0;
             mActivePointerId = ev.getPointerId(0);
@@ -1215,82 +1090,6 @@
                 } else {
                     awakenScrollBars();
                 }
-            } else if (mTouchState == TOUCH_STATE_REORDERING) {
-                // Update the last motion position
-                mLastMotionX = ev.getX();
-                mLastMotionY = ev.getY();
-
-                // Update the parent down so that our zoom animations take this new movement into
-                // account
-                float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
-                mParentDownMotionX = pt[0];
-                mParentDownMotionY = pt[1];
-                updateDragViewTranslationDuringDrag();
-
-                // Find the closest page to the touch point
-                final int dragViewIndex = indexOfChild(mDragView);
-
-                if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX);
-                if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY);
-                if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX);
-                if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY);
-
-                final int pageUnderPointIndex = getNearestHoverOverPageIndex();
-                // Do not allow any page to be moved to 0th position.
-                if (pageUnderPointIndex > 0 && pageUnderPointIndex != indexOfChild(mDragView)) {
-                    if (0 <= pageUnderPointIndex && pageUnderPointIndex <= getPageCount() - 1 &&
-                            pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
-                        mSidePageHoverIndex = pageUnderPointIndex;
-                        mSidePageHoverRunnable = new Runnable() {
-                            @Override
-                            public void run() {
-                                // Setup the scroll to the correct page before we swap the views
-                                snapToPage(pageUnderPointIndex);
-
-                                // For each of the pages between the paged view and the drag view,
-                                // animate them from the previous position to the new position in
-                                // the layout (as a result of the drag view moving in the layout)
-                                int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1;
-                                int lowerIndex = (dragViewIndex < pageUnderPointIndex) ?
-                                        dragViewIndex + 1 : pageUnderPointIndex;
-                                int upperIndex = (dragViewIndex > pageUnderPointIndex) ?
-                                        dragViewIndex - 1 : pageUnderPointIndex;
-                                for (int i = lowerIndex; i <= upperIndex; ++i) {
-                                    View v = getChildAt(i);
-                                    // dragViewIndex < pageUnderPointIndex, so after we remove the
-                                    // drag view all subsequent views to pageUnderPointIndex will
-                                    // shift down.
-                                    int oldX = getChildOffset(i);
-                                    int newX = getChildOffset(i + shiftDelta);
-
-                                    // Animate the view translation from its old position to its new
-                                    // position
-                                    ObjectAnimator anim = (ObjectAnimator) v.getTag();
-                                    if (anim != null) {
-                                        anim.cancel();
-                                    }
-
-                                    v.setTranslationX(oldX - newX);
-                                    anim = LauncherAnimUtils.ofFloat(v, View.TRANSLATION_X, 0);
-                                    anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION);
-                                    anim.start();
-                                    v.setTag(anim);
-                                }
-
-                                removeView(mDragView);
-                                addView(mDragView, pageUnderPointIndex);
-                                mSidePageHoverIndex = -1;
-                                if (mPageIndicator != null) {
-                                    mPageIndicator.setActiveMarker(getNextPage());
-                                }
-                            }
-                        };
-                        postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT);
-                    }
-                } else {
-                    removeCallbacks(mSidePageHoverRunnable);
-                    mSidePageHoverIndex = -1;
-                }
             } else {
                 determineScrollingStart(ev);
             }
@@ -1391,25 +1190,8 @@
                 } else {
                     snapToDestination();
                 }
-            } else if (mTouchState == TOUCH_STATE_REORDERING) {
-                // Update the last motion position
-                mLastMotionX = ev.getX();
-                mLastMotionY = ev.getY();
-
-                // Update the parent down so that our zoom animations take this new movement into
-                // account
-                float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
-                mParentDownMotionX = pt[0];
-                mParentDownMotionY = pt[1];
-                updateDragViewTranslationDuringDrag();
-            } else {
-                if (!mCancelTap) {
-                    onUnhandledTap(ev);
-                }
             }
 
-            // Remove the callback to wait for the side page hover timeout
-            removeCallbacks(mSidePageHoverRunnable);
             // End any intermediate reordering states
             resetTouchState();
             break;
@@ -1437,8 +1219,6 @@
 
     private void resetTouchState() {
         releaseVelocityTracker();
-        endReordering();
-        mCancelTap = false;
         mTouchState = TOUCH_STATE_REST;
         mActivePointerId = INVALID_POINTER;
     }
@@ -1452,10 +1232,6 @@
     protected void onScrollInteractionEnd() {
     }
 
-    protected void onUnhandledTap(MotionEvent ev) {
-        Launcher.getLauncher(getContext()).onClick(this);
-    }
-
     @Override
     public boolean onGenericMotionEvent(MotionEvent event) {
         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
@@ -1512,7 +1288,6 @@
             // TODO: Make this decision more intelligent.
             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
             mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
-            mLastMotionY = ev.getY(newPointerIndex);
             mLastMotionXRemainder = 0;
             mActivePointerId = ev.getPointerId(newPointerIndex);
             if (mVelocityTracker != null) {
@@ -1689,139 +1464,6 @@
         if (getNextPage() < getChildCount() -1) snapToPage(getNextPage() + 1);
     }
 
-    @Override
-    public boolean performLongClick() {
-        mCancelTap = true;
-        return super.performLongClick();
-    }
-
-    public static class SavedState extends BaseSavedState {
-        int currentPage = -1;
-
-        SavedState(Parcelable superState) {
-            super(superState);
-        }
-
-        @Thunk SavedState(Parcel in) {
-            super(in);
-            currentPage = in.readInt();
-        }
-
-        @Override
-        public void writeToParcel(Parcel out, int flags) {
-            super.writeToParcel(out, flags);
-            out.writeInt(currentPage);
-        }
-
-        public static final Parcelable.Creator<SavedState> CREATOR =
-                new Parcelable.Creator<SavedState>() {
-            public SavedState createFromParcel(Parcel in) {
-                return new SavedState(in);
-            }
-
-            public SavedState[] newArray(int size) {
-                return new SavedState[size];
-            }
-        };
-    }
-
-    // Animate the drag view back to the original position
-    private void animateDragViewToOriginalPosition() {
-        if (mDragView != null) {
-            Animator anim = LauncherAnimUtils.ofPropertyValuesHolder(mDragView,
-                    new PropertyListBuilder()
-                            .scale(1)
-                            .translationX(0)
-                            .translationY(0)
-                            .build())
-                    .setDuration(REORDERING_DROP_REPOSITION_DURATION);
-            anim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    onPostReorderingAnimationCompleted();
-                }
-            });
-            anim.start();
-        }
-    }
-
-    public void onStartReordering() {
-        // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
-        mTouchState = TOUCH_STATE_REORDERING;
-        mIsReordering = true;
-
-        // We must invalidate to trigger a redraw to update the layers such that the drag view
-        // is always drawn on top
-        invalidate();
-    }
-
-    @Thunk void onPostReorderingAnimationCompleted() {
-        // Trigger the callback when reordering has settled
-        --mPostReorderingPreZoomInRemainingAnimationCount;
-        if (mPostReorderingPreZoomInRunnable != null &&
-                mPostReorderingPreZoomInRemainingAnimationCount == 0) {
-            mPostReorderingPreZoomInRunnable.run();
-            mPostReorderingPreZoomInRunnable = null;
-        }
-    }
-
-    public void onEndReordering() {
-        mIsReordering = false;
-    }
-
-    public boolean startReordering(View v) {
-        int dragViewIndex = indexOfChild(v);
-
-        // Do not allow the first page to be moved around
-        if (mTouchState != TOUCH_STATE_REST || dragViewIndex <= 0) return false;
-
-        // Check if we are within the reordering range
-        if (0 <= dragViewIndex && dragViewIndex <= getPageCount() - 1) {
-            // Find the drag view under the pointer
-            mDragView = getChildAt(dragViewIndex);
-            mDragView.animate().scaleX(1.15f).scaleY(1.15f).setDuration(100).start();
-            mDragViewBaselineLeft = mDragView.getLeft();
-            mReorderingStarted = true;
-
-            snapToPage(getPageNearestToCenterOfScreen());
-            disableFreeScroll();
-            onStartReordering();
-            return true;
-        }
-        return false;
-    }
-
-    boolean isReordering(boolean testTouchState) {
-        boolean state = mIsReordering;
-        if (testTouchState) {
-            state &= (mTouchState == TOUCH_STATE_REORDERING);
-        }
-        return state;
-    }
-    void endReordering() {
-        // For simplicity, we call endReordering sometimes even if reordering was never started.
-        // In that case, we don't want to do anything.
-        if (!mReorderingStarted) return;
-        mReorderingStarted = false;
-
-        mPostReorderingPreZoomInRunnable = new Runnable() {
-            public void run() {
-                // If we haven't flung-to-delete the current child,
-                // then we just animate the drag view back into position
-                onEndReordering();
-
-                enableFreeScroll();
-            }
-        };
-
-        mPostReorderingPreZoomInRemainingAnimationCount =
-                NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT;
-        // Snap to the current page
-        snapToPage(indexOfChild(mDragView), 0);
-        // Animate the drag view back to the front position
-        animateDragViewToOriginalPosition();
-    }
-
     /* Accessibility */
     @SuppressWarnings("deprecation")
     @Override
@@ -1900,4 +1542,9 @@
     public boolean onHoverEvent(android.view.MotionEvent event) {
         return true;
     }
+
+    protected interface ComputePageScrollsLogic {
+
+        boolean shouldIncludeView(View view);
+    }
 }
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
index 6a4e93b..7fa0e52 100644
--- a/src/com/android/launcher3/SettingsActivity.java
+++ b/src/com/android/launcher3/SettingsActivity.java
@@ -31,11 +31,17 @@
 import android.preference.Preference;
 import android.preference.PreferenceFragment;
 import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Adapter;
 
 import com.android.launcher3.graphics.IconShapeOverride;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.util.SettingsObserver;
 import com.android.launcher3.views.ButtonPreference;
+import com.android.launcher3.views.HighlightableListView;
 
 /**
  * Settings activity for Launcher. Currently implements the following setting: Allow rotation
@@ -48,6 +54,10 @@
     /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
     private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
 
+    private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+    private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
+    private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -55,11 +65,15 @@
         if (savedInstanceState == null) {
             // Display the fragment as the main content.
             getFragmentManager().beginTransaction()
-                    .replace(android.R.id.content, new LauncherSettingsFragment())
+                    .replace(android.R.id.content, getNewFragment())
                     .commit();
         }
     }
 
+    protected PreferenceFragment getNewFragment() {
+        return new LauncherSettingsFragment();
+    }
+
     /**
      * This fragment shows the launcher preferences.
      */
@@ -67,9 +81,22 @@
 
         private IconBadgingObserver mIconBadgingObserver;
 
+        private String mPreferenceKey;
+        private boolean mPreferenceHighlighted = false;
+
+        @Override
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            return inflater.inflate(R.layout.launcher_preference, container, false);
+        }
+
         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
+            if (savedInstanceState != null) {
+                mPreferenceHighlighted = savedInstanceState.getBoolean(SAVE_HIGHLIGHTED_KEY);
+            }
+
             getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
             addPreferencesFromResource(R.xml.launcher_preferences);
 
@@ -101,6 +128,43 @@
         }
 
         @Override
+        public void onSaveInstanceState(Bundle outState) {
+            super.onSaveInstanceState(outState);
+            outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
+        }
+
+        @Override
+        public void onResume() {
+            super.onResume();
+
+            Intent intent = getActivity().getIntent();
+            mPreferenceKey = intent.getStringExtra(EXTRA_FRAGMENT_ARG_KEY);
+            if (isAdded() && !mPreferenceHighlighted && !TextUtils.isEmpty(mPreferenceKey)) {
+                getView().postDelayed(this::highlightPreference, DELAY_HIGHLIGHT_DURATION_MILLIS);
+            }
+        }
+
+        private void highlightPreference() {
+            HighlightableListView list = getView().findViewById(android.R.id.list);
+            Preference pref = findPreference(mPreferenceKey);
+            Adapter adapter = list.getAdapter();
+            if (adapter == null) {
+                return;
+            }
+
+            // Find the position
+            int position = -1;
+            for (int i = adapter.getCount() - 1; i >= 0; i--) {
+                if (pref == adapter.getItem(i)) {
+                    position = i;
+                    break;
+                }
+            }
+            list.highlightPosition(position);
+            mPreferenceHighlighted = true;
+        }
+
+        @Override
         public void onDestroy() {
             if (mIconBadgingObserver != null) {
                 mIconBadgingObserver.unregister();
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index f329f5e..68ad253 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -59,7 +59,6 @@
 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
-import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
@@ -82,6 +81,7 @@
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
 import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.touch.WorkspaceTouchListener;
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -242,7 +242,6 @@
     private Runnable mOnOverlayHiddenCallback;
 
     private boolean mForceDrawAdjacentPages = false;
-    private boolean mPageRearrangeEnabled = false;
 
     // Total over scrollX in the overlay direction.
     private float mOverlayTranslation;
@@ -250,8 +249,6 @@
     // Handles workspace state transitions
     private final WorkspaceStateTransitionAnimation mStateTransitionAnimation;
 
-    private AccessibilityDelegate mPagesAccessibilityDelegate;
-
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -286,6 +283,7 @@
 
         // Attach a scrim
         new WorkspaceAndHotseatScrim(this).attach();
+        setOnTouchListener(new WorkspaceTouchListener(mLauncher, this));
     }
 
     @Override
@@ -475,7 +473,6 @@
         }
         CellLayout cl = ((CellLayout) child);
         cl.setOnInterceptTouchListener(this);
-        cl.setClickable(true);
         cl.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
         super.onViewAdded(child);
     }
@@ -555,10 +552,6 @@
         // created CellLayout.
         CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
                         R.layout.workspace_screen, this, false /* attachToRoot */);
-        newScreen.setOnLongClickListener(mLongClickListener);
-        newScreen.setOnClickListener(mLauncher);
-        newScreen.setSoundEffectsEnabled(false);
-
         int paddingLeftRight = mLauncher.getDeviceProfile().cellLayoutPaddingLeftRightPx;
         int paddingBottom = mLauncher.getDeviceProfile().cellLayoutBottomPaddingPx;
         newScreen.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
@@ -938,7 +931,6 @@
 
         child.setHapticFeedbackEnabled(false);
         child.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
-
         if (child instanceof DropTarget) {
             mDragController.addDropTarget((DropTarget) child);
         }
@@ -1378,8 +1370,7 @@
     }
 
     private void updateChildrenLayersEnabled() {
-        boolean enableChildrenLayers =
-                isPageRearrangeEnabled() || mIsSwitchingState || isPageInTransition();
+        boolean enableChildrenLayers = mIsSwitchingState || isPageInTransition();
 
         if (enableChildrenLayers != mChildrenLayersEnabled) {
             mChildrenLayersEnabled = enableChildrenLayers;
@@ -1463,40 +1454,6 @@
         mOutlineProvider = outlineProvider;
     }
 
-    public void onStartReordering() {
-        super.onStartReordering();
-        // Reordering handles its own animations, disable the automatic ones.
-        disableLayoutTransitions();
-    }
-
-    public void onEndReordering() {
-        super.onEndReordering();
-
-        if (mLauncher.isWorkspaceLoading()) {
-            // Invalid and dangerous operation if workspace is loading
-            return;
-        }
-
-        ArrayList<Long> prevScreenOrder = (ArrayList<Long>) mScreenOrder.clone();
-        mScreenOrder.clear();
-        int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            CellLayout cl = ((CellLayout) getChildAt(i));
-            mScreenOrder.add(getIdForScreen(cl));
-        }
-
-        for (int i = 0; i < prevScreenOrder.size(); i++) {
-            if (mScreenOrder.get(i) != prevScreenOrder.get(i)) {
-                mLauncher.getUserEventDispatcher().logOverviewReorder();
-                break;
-            }
-        }
-        LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
-
-        // Re-enable auto layout transitions for page deletion.
-        enableLayoutTransitions();
-    }
-
     public void snapToPageFromOverView(int whichPage) {
         snapToPage(whichPage, OVERVIEW_TRANSITION_MS, Interpolators.ZOOM_IN);
     }
@@ -1556,47 +1513,17 @@
         if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
             int total = getPageCount();
             for (int i = 0; i < total; i++) {
-                updateAccessibilityFlags(accessibilityFlag, (CellLayout) getPageAt(i), i);
+                updateAccessibilityFlags(accessibilityFlag, (CellLayout) getPageAt(i));
             }
             setImportantForAccessibility(accessibilityFlag);
         }
     }
 
-    private void updateAccessibilityFlags(int accessibilityFlag, CellLayout page, int pageNo) {
-        if (isPageRearrangeEnabled()) {
-            page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-            page.getShortcutsAndWidgets().setImportantForAccessibility(
-                    IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
-            page.setContentDescription(getPageDescription(pageNo));
-
-            // No custom action for the first page.
-            if (!FeatureFlags.QSB_ON_FIRST_SCREEN || pageNo > 0) {
-                if (mPagesAccessibilityDelegate == null) {
-                    mPagesAccessibilityDelegate = new OverviewScreenAccessibilityDelegate(this);
-                }
-                page.setAccessibilityDelegate(mPagesAccessibilityDelegate);
-            }
-        } else {
-            page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
-            page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
-            page.setContentDescription(null);
-            page.setAccessibilityDelegate(null);
-        }
-    }
-
-    public void setPageRearrangeEnabled(boolean isEnabled) {
-        if (mPageRearrangeEnabled != isEnabled) {
-            mPageRearrangeEnabled = isEnabled;
-            if (isEnabled) {
-                enableFreeScroll();
-            } else {
-                disableFreeScroll();
-            }
-        }
-    }
-
-    public boolean isPageRearrangeEnabled() {
-        return mPageRearrangeEnabled;
+    private void updateAccessibilityFlags(int accessibilityFlag, CellLayout page) {
+        page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+        page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
+        page.setContentDescription(null);
+        page.setAccessibilityDelegate(null);
     }
 
     public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
@@ -1612,10 +1539,6 @@
                 protected void enableAccessibleDrag(boolean enable) {
                     super.enableAccessibleDrag(enable);
                     setEnableForLayout(mLauncher.getHotseat().getLayout(),enable);
-
-                    // We need to allow our individual children to become click handlers in this
-                    // case, so temporarily unset the click handlers.
-                    setOnClickListener(enable ? null : mLauncher);
                 }
             });
         }
@@ -3331,8 +3254,7 @@
                         && v instanceof FolderIcon) {
                     FolderBadgeInfo folderBadgeInfo = new FolderBadgeInfo();
                     for (ShortcutInfo si : ((FolderInfo) info).contents) {
-                        folderBadgeInfo.addBadgeInfo(mLauncher.getPopupDataProvider()
-                                .getBadgeInfoForItem(si));
+                        folderBadgeInfo.addBadgeInfo(mLauncher.getBadgeInfoForItem(si));
                     }
                     ((FolderIcon) v).setBadgeInfo(folderBadgeInfo);
                 }
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index f6d0248..63c1181 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -18,6 +18,8 @@
 
 import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherState.HOTSEAT;
+import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 
 import android.animation.Animator;
@@ -32,65 +34,14 @@
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.graphics.ViewScrim;
 
 /**
- * A convenience class to update a view's visibility state after an alpha animation.
- */
-class AlphaUpdateListener extends AnimatorListenerAdapter implements ValueAnimator.AnimatorUpdateListener {
-    private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
-
-    private View mView;
-    private boolean mAccessibilityEnabled;
-    private boolean mCanceled = false;
-
-    public AlphaUpdateListener(View v, boolean accessibilityEnabled) {
-        mView = v;
-        mAccessibilityEnabled = accessibilityEnabled;
-    }
-
-    @Override
-    public void onAnimationUpdate(ValueAnimator arg0) {
-        updateVisibility(mView, mAccessibilityEnabled);
-    }
-
-    public static void updateVisibility(View view, boolean accessibilityEnabled) {
-        // We want to avoid the extra layout pass by setting the views to GONE unless
-        // accessibility is on, in which case not setting them to GONE causes a glitch.
-        int invisibleState = accessibilityEnabled ? View.GONE : View.INVISIBLE;
-        if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
-            view.setVisibility(invisibleState);
-        } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
-                && view.getVisibility() != View.VISIBLE) {
-            view.setVisibility(View.VISIBLE);
-        }
-    }
-
-    @Override
-    public void onAnimationCancel(Animator animation) {
-        mCanceled = true;
-    }
-
-    @Override
-    public void onAnimationEnd(Animator arg0) {
-        if (mCanceled) return;
-        updateVisibility(mView, mAccessibilityEnabled);
-    }
-
-    @Override
-    public void onAnimationStart(Animator arg0) {
-        // We want the views to be visible for animation, so fade-in/out is visible
-        mView.setVisibility(View.VISIBLE);
-    }
-}
-
-/**
  * Manages the animations between each of the workspace states.
  */
 public class WorkspaceStateTransitionAnimation {
 
-    public static final PropertySetter NO_ANIM_PROPERTY_SETTER = new PropertySetter();
-
     private final Launcher mLauncher;
     private final Workspace mWorkspace;
 
@@ -107,9 +58,7 @@
 
     public void setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder,
             AnimationConfig config) {
-        AnimatedPropertySetter propertySetter =
-                new AnimatedPropertySetter(config.duration, builder);
-        setWorkspaceProperty(toState, propertySetter);
+        setWorkspaceProperty(toState, config.getProperSetter(builder));
     }
 
     public float getFinalScale() {
@@ -135,10 +84,12 @@
         propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
                 scaleAndTranslation[2], Interpolators.ZOOM_IN);
 
-        propertySetter.setViewAlpha(mLauncher.getHotseat(), state.getHoseatAlpha(mLauncher),
+        int elements = state.getVisibleElements(mLauncher);
+        float hotseatAlpha = (elements & HOTSEAT) != 0 ? 1 : 0;
+        propertySetter.setViewAlpha(mLauncher.getHotseat(), hotseatAlpha,
                 pageAlphaProvider.interpolator);
         propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
-                state.getHoseatAlpha(mLauncher), pageAlphaProvider.interpolator);
+                hotseatAlpha, pageAlphaProvider.interpolator);
 
         // Set scrim
         propertySetter.setFloat(ViewScrim.get(mWorkspace), ViewScrim.PROGRESS,
@@ -162,71 +113,4 @@
         propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
                 pageAlpha, pageAlphaProvider.interpolator);
     }
-
-    public static class PropertySetter {
-
-        public void setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
-            view.setAlpha(alpha);
-            AlphaUpdateListener.updateVisibility(view, isAccessibilityEnabled(view.getContext()));
-        }
-
-        public <T> void setFloat(T target, Property<T, Float> property, float value,
-                TimeInterpolator interpolator) {
-            property.set(target, value);
-        }
-
-        public <T> void setInt(T target, Property<T, Integer> property, int value,
-                TimeInterpolator interpolator) {
-            property.set(target, value);
-        }
-    }
-
-    public static class AnimatedPropertySetter extends PropertySetter {
-
-        private final long mDuration;
-        private final AnimatorSetBuilder mStateAnimator;
-
-        public AnimatedPropertySetter(long duration, AnimatorSetBuilder builder) {
-            mDuration = duration;
-            mStateAnimator = builder;
-        }
-
-        @Override
-        public void setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
-            if (view.getAlpha() == alpha) {
-                return;
-            }
-            ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.ALPHA, alpha);
-            anim.addListener(new AlphaUpdateListener(
-                    view, isAccessibilityEnabled(view.getContext())));
-            anim.setDuration(mDuration).setInterpolator(interpolator);
-            mStateAnimator.play(anim);
-        }
-
-        @Override
-        public <T> void setFloat(T target, Property<T, Float> property, float value,
-                TimeInterpolator interpolator) {
-            if (property.get(target) == value) {
-                return;
-            }
-            Animator anim = ObjectAnimator.ofFloat(target, property, value);
-            anim.setDuration(mDuration).setInterpolator(interpolator);
-            mStateAnimator.play(anim);
-        }
-
-        @Override
-        public <T> void setInt(T target, Property<T, Integer> property, int value,
-                TimeInterpolator interpolator) {
-            if (property.get(target) == value) {
-                return;
-            }
-            Animator anim = ObjectAnimator.ofInt(target, property, value);
-            anim.setDuration(mDuration).setInterpolator(interpolator);
-            mStateAnimator.play(anim);
-        }
-
-        private TimeInterpolator getFadeInterpolator(float finalAlpha) {
-            return finalAlpha == 0 ? Interpolators.DEACCEL_2 : null;
-        }
-    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
deleted file mode 100644
index f9eb2ed..0000000
--- a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2015 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.accessibility;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.util.SparseArray;
-import android.view.View;
-import android.view.View.AccessibilityDelegate;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
-import com.android.launcher3.config.FeatureFlags;
-
-public class OverviewScreenAccessibilityDelegate extends AccessibilityDelegate {
-
-    private static final int MOVE_BACKWARD = R.id.action_move_screen_backwards;
-    private static final int MOVE_FORWARD = R.id.action_move_screen_forwards;
-
-    private final SparseArray<AccessibilityAction> mActions = new SparseArray<>();
-    private final Workspace mWorkspace;
-
-    public OverviewScreenAccessibilityDelegate(Workspace workspace) {
-        mWorkspace = workspace;
-
-        Context context = mWorkspace.getContext();
-        boolean isRtl = Utilities.isRtl(context.getResources());
-        mActions.put(MOVE_BACKWARD, new AccessibilityAction(MOVE_BACKWARD,
-                context.getText(isRtl ? R.string.action_move_screen_right :
-                    R.string.action_move_screen_left)));
-        mActions.put(MOVE_FORWARD, new AccessibilityAction(MOVE_FORWARD,
-                context.getText(isRtl ? R.string.action_move_screen_left :
-                    R.string.action_move_screen_right)));
-    }
-
-    @Override
-    public boolean performAccessibilityAction(View host, int action, Bundle args) {
-        if (host != null) {
-            if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS ) {
-                int index = mWorkspace.indexOfChild(host);
-                mWorkspace.setCurrentPage(index);
-            } else if (action == MOVE_FORWARD) {
-                movePage(mWorkspace.indexOfChild(host) + 1, host);
-                return true;
-            } else if (action == MOVE_BACKWARD) {
-                movePage(mWorkspace.indexOfChild(host) - 1, host);
-                return true;
-            }
-        }
-
-        return super.performAccessibilityAction(host, action, args);
-    }
-
-    private void movePage(int finalIndex, View view) {
-        mWorkspace.onStartReordering();
-        mWorkspace.removeView(view);
-        mWorkspace.addView(view, finalIndex);
-        mWorkspace.onEndReordering();
-        mWorkspace.announceForAccessibility(mWorkspace.getContext().getText(R.string.screen_moved));
-
-        mWorkspace.updateAccessibilityFlags();
-        view.performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
-    }
-
-    @Override
-    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
-        super.onInitializeAccessibilityNodeInfo(host, info);
-
-        int index = mWorkspace.indexOfChild(host);
-        if (index < mWorkspace.getChildCount() - 1) {
-            info.addAction(mActions.get(MOVE_FORWARD));
-        }
-
-        int startIndex = FeatureFlags.QSB_ON_FIRST_SCREEN ? 1 : 0;
-        if (index > startIndex) {
-            info.addAction(mActions.get(MOVE_BACKWARD));
-        }
-    }
-}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 3fe5d7a..8f5fcf5 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -25,6 +25,7 @@
 import android.os.Process;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.text.Selection;
@@ -192,6 +193,19 @@
         return false;
     }
 
+    public String getDescription() {
+        @StringRes int descriptionRes;
+        if (mUsingTabs) {
+            descriptionRes =
+                    mViewPager.getNextPage() == 0
+                            ? R.string.all_apps_button_personal_label
+                            : R.string.all_apps_button_work_label;
+        } else {
+            descriptionRes = R.string.all_apps_button_label;
+        }
+        return getContext().getString(descriptionRes);
+    }
+
     public AllAppsRecyclerView getActiveRecyclerView() {
         if (!mUsingTabs || mViewPager.getNextPage() == 0) {
             return mAH[AdapterHolder.MAIN].recyclerView;
@@ -203,14 +217,14 @@
     /**
      * Resets the state of AllApps.
      */
-    public void reset() {
+    public void reset(boolean animate) {
         for (int i = 0; i < mAH.length; i++) {
             if (mAH[i].recyclerView != null) {
                 mAH[i].recyclerView.scrollToTop();
             }
         }
         if (isHeaderVisible()) {
-            mHeader.reset();
+            mHeader.reset(animate);
         }
         // Reset the search bar and base recycler view after transitioning home
         mSearchUiManager.resetSearch();
@@ -346,7 +360,7 @@
 
     public void onTabChanged(int pos) {
         mHeader.setMainActive(pos == 0);
-        reset();
+        reset(true /* animate */);
         if (mAH[pos].recyclerView != null) {
             mAH[pos].recyclerView.bindFastScrollbar();
 
@@ -369,6 +383,18 @@
         return mHeader;
     }
 
+    public View getSearchView() {
+        return mSearchContainer;
+    }
+
+    public View getContentView() {
+        return mViewPager == null ? getActiveRecyclerView() : mViewPager;
+    }
+
+    public RecyclerViewFastScroller getScrollBar() {
+        return getActiveRecyclerView().getScrollbar();
+    }
+
     public void setupHeader() {
         mHeader.setVisibility(View.VISIBLE);
         mHeader.setup(mAH, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView == null);
diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java
index 3b4450b..b2e35a4 100644
--- a/src/com/android/launcher3/allapps/AllAppsPagedView.java
+++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java
@@ -17,10 +17,9 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-
 import android.view.MotionEvent;
+
 import com.android.launcher3.PagedView;
-import com.android.launcher3.R;
 
 public class AllAppsPagedView extends PagedView<PersonalWorkSlidingTabStrip> {
 
@@ -42,8 +41,8 @@
 
     @Override
     protected String getCurrentPageDescription() {
-        return getResources().getString(
-                getNextPage() == 0 ? R.string.all_apps_personal_tab : R.string.all_apps_work_tab);
+        // Not necessary, tab-bar already has two tabs with their own descriptions.
+        return "";
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 13a42f1..bf8f531 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -1,7 +1,10 @@
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
+import static com.android.launcher3.LauncherState.ALL_APPS_HEADER;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
 
 import android.animation.Animator;
@@ -13,16 +16,15 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
-import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.SearchUiManager.OnScrollRangeChangeListener;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.util.Themes;
 
 /**
@@ -55,7 +57,6 @@
     public static final float PARALLAX_COEFFICIENT = .125f;
 
     private AllAppsContainerView mAppsView;
-    private Hotseat mHotseat;
 
     private final Launcher mLauncher;
     private final boolean mIsDarkTheme;
@@ -88,7 +89,6 @@
 
     private void onProgressAnimationStart() {
         // Initialize values that should not change until #onDragEnd
-        mHotseat.setVisibility(View.VISIBLE);
         mAppsView.setVisibility(View.VISIBLE);
     }
 
@@ -116,14 +116,10 @@
         mProgress = progress;
         float shiftCurrent = progress * mShiftRange;
 
-        float workspaceHotseatAlpha = Utilities.boundToRange(progress, 0f, 1f);
-        float alpha = 1 - workspaceHotseatAlpha;
-
         mAppsView.setTranslationY(shiftCurrent);
         float hotseatTranslation = -mShiftRange + shiftCurrent;
 
         if (!mIsVerticalLayout) {
-            mAppsView.setAlpha(alpha);
             mLauncher.getHotseat().setTranslationY(hotseatTranslation);
             mLauncher.getWorkspace().getPageIndicator().setTranslationY(hotseatTranslation);
         }
@@ -149,6 +145,7 @@
     @Override
     public void setState(LauncherState state) {
         setProgress(state.getVerticalProgress(mLauncher));
+        setAlphas(state, NO_ANIM_PROPERTY_SETTER);
         onProgressAnimationEnd();
     }
 
@@ -161,6 +158,7 @@
             AnimatorSetBuilder builder, AnimationConfig config) {
         float targetProgress = toState.getVerticalProgress(mLauncher);
         if (Float.compare(mProgress, targetProgress) == 0) {
+            setAlphas(toState, config.getProperSetter(builder));
             // Fail fast
             onProgressAnimationEnd();
             return;
@@ -174,6 +172,19 @@
         anim.addListener(getProgressAnimatorListener());
 
         builder.play(anim);
+
+        setAlphas(toState, config.getProperSetter(builder));
+    }
+
+    private void setAlphas(LauncherState toState, PropertySetter setter) {
+        int visibleElements = toState.getVisibleElements(mLauncher);
+        boolean hasHeader = (visibleElements & ALL_APPS_HEADER) != 0;
+        boolean hasContent = (visibleElements & ALL_APPS_CONTENT) != 0;
+
+        setter.setViewAlpha(mAppsView.getSearchView(), hasHeader ? 1 : 0, LINEAR);
+        setter.setViewAlpha(mAppsView.getContentView(), hasContent ? 1 : 0, LINEAR);
+        setter.setViewAlpha(mAppsView.getScrollBar(), hasContent ? 1 : 0, LINEAR);
+        mAppsView.getFloatingHeaderView().setContentVisibility(hasHeader, hasContent, setter);
     }
 
     public AnimatorListenerAdapter getProgressAnimatorListener() {
@@ -190,10 +201,8 @@
         };
     }
 
-    public void setupViews(AllAppsContainerView appsView, Hotseat hotseat) {
+    public void setupViews(AllAppsContainerView appsView) {
         mAppsView = appsView;
-        mHotseat = hotseat;
-        mHotseat.bringToFront();
         mAppsView.getSearchUiManager().addOnScrollRangeChangeListener(this);
     }
 
@@ -210,15 +219,12 @@
     private void onProgressAnimationEnd() {
         if (Float.compare(mProgress, 1f) == 0) {
             mAppsView.setVisibility(View.INVISIBLE);
-            mHotseat.setVisibility(View.VISIBLE);
-            mAppsView.reset();
+            mAppsView.reset(false /* animate */);
         } else if (Float.compare(mProgress, 0f) == 0) {
-            mHotseat.setVisibility(View.INVISIBLE);
             mAppsView.setVisibility(View.VISIBLE);
             mAppsView.onScrollUpEnd();
         } else {
             mAppsView.setVisibility(View.VISIBLE);
-            mHotseat.setVisibility(View.VISIBLE);
         }
     }
 }
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index a0dc5a3..461f5b5 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Point;
@@ -29,6 +31,7 @@
 import android.widget.LinearLayout;
 
 import com.android.launcher3.R;
+import com.android.launcher3.anim.PropertySetter;
 
 public class FloatingHeaderView extends LinearLayout implements
         ValueAnimator.AnimatorUpdateListener {
@@ -57,7 +60,7 @@
         }
     };
 
-    private ViewGroup mTabLayout;
+    protected ViewGroup mTabLayout;
     private AllAppsRecyclerView mMainRV;
     private AllAppsRecyclerView mWorkRV;
     private AllAppsRecyclerView mCurrentRV;
@@ -65,6 +68,8 @@
     private boolean mHeaderCollapsed;
     private int mSnappedScrolledY;
     private int mTranslationY;
+
+    private boolean mAllowTouchForwarding;
     private boolean mForwardToRecyclerView;
 
     protected boolean mTabsHidden;
@@ -91,7 +96,7 @@
         mWorkRV = setupRV(mWorkRV, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView);
         mParent = (ViewGroup) mMainRV.getParent();
         setMainActive(true);
-        reset();
+        reset(false);
     }
 
     private AllAppsRecyclerView setupRV(AllAppsRecyclerView old, AllAppsRecyclerView updated) {
@@ -158,12 +163,19 @@
         }
     }
 
-    public void reset() {
-        int translateTo = 0;
-        mAnimator.setIntValues(mTranslationY, translateTo);
-        mAnimator.addUpdateListener(this);
-        mAnimator.setDuration(150);
-        mAnimator.start();
+    public void reset(boolean animate) {
+        if (mAnimator.isStarted()) {
+            mAnimator.cancel();
+        }
+        if (animate) {
+            mAnimator.setIntValues(mTranslationY, 0);
+            mAnimator.addUpdateListener(this);
+            mAnimator.setDuration(150);
+            mAnimator.start();
+        } else {
+            mTranslationY = 0;
+            apply();
+        }
         mHeaderCollapsed = false;
         mSnappedScrolledY = -mMaxTranslation;
         mCurrentRV.scrollToTop();
@@ -181,6 +193,10 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (!mAllowTouchForwarding) {
+            mForwardToRecyclerView = false;
+            return super.onInterceptTouchEvent(ev);
+        }
         calcOffset(mTempOffset);
         ev.offsetLocation(mTempOffset.x, mTempOffset.y);
         mForwardToRecyclerView = mCurrentRV.onInterceptTouchEvent(ev);
@@ -208,6 +224,19 @@
         p.x = getLeft() - mCurrentRV.getLeft() - mParent.getLeft();
         p.y = getTop() - mCurrentRV.getTop() - mParent.getTop();
     }
+
+    public void setContentVisibility(boolean hasHeader, boolean hasContent, PropertySetter setter) {
+        setter.setViewAlpha(this, hasContent ? 1 : 0, LINEAR);
+        allowTouchForwarding(hasContent);
+    }
+
+    protected void allowTouchForwarding(boolean allow) {
+        mAllowTouchForwarding = allow;
+    }
+
+    public boolean hasVisibleContent() {
+        return false;
+    }
 }
 
 
diff --git a/src/com/android/launcher3/anim/AlphaUpdateListener.java b/src/com/android/launcher3/anim/AlphaUpdateListener.java
new file mode 100644
index 0000000..04d97a7
--- /dev/null
+++ b/src/com/android/launcher3/anim/AlphaUpdateListener.java
@@ -0,0 +1,73 @@
+/*
+ * 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.anim;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.view.View;
+
+/**
+ * A convenience class to update a view's visibility state after an alpha animation.
+ */
+public class AlphaUpdateListener extends AnimatorListenerAdapter implements AnimatorUpdateListener {
+    private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
+
+    private View mView;
+    private boolean mAccessibilityEnabled;
+    private boolean mCanceled = false;
+
+    public AlphaUpdateListener(View v, boolean accessibilityEnabled) {
+        mView = v;
+        mAccessibilityEnabled = accessibilityEnabled;
+    }
+
+    @Override
+    public void onAnimationUpdate(ValueAnimator arg0) {
+        updateVisibility(mView, mAccessibilityEnabled);
+    }
+
+    public static void updateVisibility(View view, boolean accessibilityEnabled) {
+        // We want to avoid the extra layout pass by setting the views to GONE unless
+        // accessibility is on, in which case not setting them to GONE causes a glitch.
+        int invisibleState = accessibilityEnabled ? View.GONE : View.INVISIBLE;
+        if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
+            view.setVisibility(invisibleState);
+        } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
+                && view.getVisibility() != View.VISIBLE) {
+            view.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
+    public void onAnimationCancel(Animator animation) {
+        mCanceled = true;
+    }
+
+    @Override
+    public void onAnimationEnd(Animator arg0) {
+        if (mCanceled) return;
+        updateVisibility(mView, mAccessibilityEnabled);
+    }
+
+    @Override
+    public void onAnimationStart(Animator arg0) {
+        // We want the views to be visible for animation, so fade-in/out is visible
+        mView.setVisibility(View.VISIBLE);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/anim/PropertySetter.java b/src/com/android/launcher3/anim/PropertySetter.java
new file mode 100644
index 0000000..51580b1
--- /dev/null
+++ b/src/com/android/launcher3/anim/PropertySetter.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 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.anim;
+
+import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.util.Property;
+import android.view.View;
+
+/**
+ * Utility class for setting a property with or without animation
+ */
+public class PropertySetter {
+
+    public static final PropertySetter NO_ANIM_PROPERTY_SETTER = new PropertySetter();
+
+    public void setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
+        view.setAlpha(alpha);
+        AlphaUpdateListener.updateVisibility(view, isAccessibilityEnabled(view.getContext()));
+    }
+
+    public <T> void setFloat(T target, Property<T, Float> property, float value,
+            TimeInterpolator interpolator) {
+        property.set(target, value);
+    }
+
+    public <T> void setInt(T target, Property<T, Integer> property, int value,
+            TimeInterpolator interpolator) {
+        property.set(target, value);
+    }
+
+    public static class AnimatedPropertySetter extends PropertySetter {
+
+        private final long mDuration;
+        private final AnimatorSetBuilder mStateAnimator;
+
+        public AnimatedPropertySetter(long duration, AnimatorSetBuilder builder) {
+            mDuration = duration;
+            mStateAnimator = builder;
+        }
+
+        @Override
+        public void setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
+            if (view.getAlpha() == alpha) {
+                return;
+            }
+            ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.ALPHA, alpha);
+            anim.addListener(new AlphaUpdateListener(
+                    view, isAccessibilityEnabled(view.getContext())));
+            anim.setDuration(mDuration).setInterpolator(interpolator);
+            mStateAnimator.play(anim);
+        }
+
+        @Override
+        public <T> void setFloat(T target, Property<T, Float> property, float value,
+                TimeInterpolator interpolator) {
+            if (property.get(target) == value) {
+                return;
+            }
+            Animator anim = ObjectAnimator.ofFloat(target, property, value);
+            anim.setDuration(mDuration).setInterpolator(interpolator);
+            mStateAnimator.play(anim);
+        }
+
+        @Override
+        public <T> void setInt(T target, Property<T, Integer> property, int value,
+                TimeInterpolator interpolator) {
+            if (property.get(target) == value) {
+                return;
+            }
+            Animator anim = ObjectAnimator.ofInt(target, property, value);
+            anim.setDuration(mDuration).setInterpolator(interpolator);
+            mStateAnimator.play(anim);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 28645dc..78ea419 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -51,6 +51,4 @@
 
     // When enabled shows a work profile tab in all apps
     public static final boolean ALL_APPS_TABS_ENABLED = true;
-
-    public static final boolean ENABLE_TWO_SWIPE_TARGETS = true;
 }
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index f5d0b24..8519365 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -31,21 +31,17 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Interpolator;
-import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DropTargetBar;
-import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
@@ -53,24 +49,20 @@
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.Thunk;
-import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
 
 import java.util.ArrayList;
 
 /**
  * A ViewGroup that coordinates dragging across its descendants
  */
-public class DragLayer extends InsettableFrameLayout {
+public class DragLayer extends BaseDragLayer<Launcher> {
 
     public static final int ANIMATION_END_DISAPPEAR = 0;
     public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
 
-    private final int[] mTmpXY = new int[2];
-
     @Thunk DragController mDragController;
 
-    private Launcher mLauncher;
-
     // Variables relating to animation of views after drop
     private ValueAnimator mDropAnim = null;
     private final TimeInterpolator mCubicEaseOutInterpolator = Interpolators.DEACCEL_1_5;
@@ -79,9 +71,6 @@
     @Thunk View mAnchorView = null;
 
     private boolean mHoverPointClosesFolder = false;
-    private final Rect mHitRect = new Rect();
-
-    private TouchCompleteListener mTouchCompleteListener;
 
     private int mTopViewIndex;
     private int mChildCountOnLastUpdate = -1;
@@ -89,8 +78,6 @@
     // Related to adjacent page hints
     private final ViewGroupFocusHelper mFocusIndicatorHelper;
 
-    protected TouchController[] mControllers;
-    private TouchController mActiveController;
     /**
      * Used to create a new DragLayer from XML.
      *
@@ -107,10 +94,9 @@
         mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
     }
 
-    public void setup(Launcher launcher, DragController dragController) {
-        mLauncher = launcher;
+    public void setup(DragController dragController) {
         mDragController = dragController;
-        mControllers = UiFactory.createTouchControllers(mLauncher);
+        mControllers = UiFactory.createTouchControllers(mActivity);
     }
 
     public ViewGroupFocusHelper getFocusIndicatorHelper() {
@@ -123,7 +109,7 @@
     }
 
     public boolean isEventOverHotseat(MotionEvent ev) {
-        return isEventOverView(mLauncher.getHotseat(), ev);
+        return isEventOverView(mActivity.getHotseat(), ev);
     }
 
     private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
@@ -131,12 +117,7 @@
     }
 
     private boolean isEventOverDropTargetBar(MotionEvent ev) {
-        return isEventOverView(mLauncher.getDropTargetBar(), ev);
-    }
-
-    public boolean isEventOverView(View view, MotionEvent ev) {
-        getDescendantRectRelativeToSelf(view, mHitRect);
-        return mHitRect.contains((int) ev.getX(), (int) ev.getY());
+        return isEventOverView(mActivity.getDropTargetBar(), ev);
     }
 
     @Override
@@ -149,66 +130,33 @@
     }
 
     @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        int action = ev.getAction();
-
-        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
-            if (mTouchCompleteListener != null) {
-                mTouchCompleteListener.onTouchComplete();
-            }
-            mTouchCompleteListener = null;
-        } else if (action == MotionEvent.ACTION_DOWN) {
-            mLauncher.finishAutoCancelActionMode();
-        }
-        return findActiveController(ev);
-    }
-
-    private boolean findActiveController(MotionEvent ev) {
-        mActiveController = null;
-
-        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
-        if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
-            mActiveController = topView;
-            return true;
-        }
-
-        if (mLauncher.getStateManager().getState().disableInteraction) {
+    protected boolean findActiveController(MotionEvent ev) {
+        if (mActivity.getStateManager().getState().disableInteraction) {
             // You Shall Not Pass!!!
+            mActiveController = null;
             return true;
         }
-
-        if (mDragController.onControllerInterceptTouchEvent(ev)) {
-            mActiveController = mDragController;
-            return true;
-        }
-
-        for (TouchController controller : mControllers) {
-            if (controller.onControllerInterceptTouchEvent(ev)) {
-                mActiveController = controller;
-                return true;
-            }
-        }
-        return false;
+        return super.findActiveController(ev);
     }
 
     @Override
     public boolean onInterceptHoverEvent(MotionEvent ev) {
-        if (mLauncher == null || mLauncher.getWorkspace() == null) {
+        if (mActivity == null || mActivity.getWorkspace() == null) {
             return false;
         }
-        Folder currentFolder = Folder.getOpen(mLauncher);
+        Folder currentFolder = Folder.getOpen(mActivity);
         if (currentFolder == null) {
             return false;
         } else {
-                AccessibilityManager accessibilityManager = (AccessibilityManager)
-                        getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+            AccessibilityManager accessibilityManager = (AccessibilityManager)
+                    getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
             if (accessibilityManager.isTouchExplorationEnabled()) {
                 final int action = ev.getAction();
                 boolean isOverFolderOrSearchBar;
                 switch (action) {
                     case MotionEvent.ACTION_HOVER_ENTER:
                         isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) ||
-                            (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
+                                (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
                         if (!isOverFolderOrSearchBar) {
                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
                             mHoverPointClosesFolder = true;
@@ -218,7 +166,7 @@
                         break;
                     case MotionEvent.ACTION_HOVER_MOVE:
                         isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) ||
-                            (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
+                                (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
                         if (!isOverFolderOrSearchBar && !mHoverPointClosesFolder) {
                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
                             mHoverPointClosesFolder = true;
@@ -239,14 +187,22 @@
                 this, AccessibilityEvent.TYPE_VIEW_FOCUSED, getContext().getString(stringId));
     }
 
+    @Override
+    public boolean onHoverEvent(MotionEvent ev) {
+        // If we've received this, we've already done the necessary handling
+        // in onInterceptHoverEvent. Return true to consume the event.
+        return false;
+    }
+
+
     private boolean isInAccessibleDrag() {
-        return mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
+        return mActivity.getAccessibilityDelegate().isInAccessibleDrag();
     }
 
     @Override
     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
         // Shortcuts can appear above folder
-        View topView = AbstractFloatingView.getTopOpenView(mLauncher);
+        View topView = AbstractFloatingView.getTopOpenView(mActivity);
         if (topView != null) {
             if (child == topView) {
                 return super.onRequestSendAccessibilityEvent(child, event);
@@ -263,13 +219,13 @@
 
     @Override
     public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
-        View topView = AbstractFloatingView.getTopOpenView(mLauncher);
+        View topView = AbstractFloatingView.getTopOpenView(mActivity);
         if (topView != null) {
             // Only add the top view as a child for accessibility when it is open
             childrenForAccessibility.add(topView);
 
             if (isInAccessibleDrag()) {
-                childrenForAccessibility.add(mLauncher.getDropTargetBar());
+                childrenForAccessibility.add(mActivity.getDropTargetBar());
             }
         } else {
             super.addChildrenForAccessibility(childrenForAccessibility);
@@ -277,103 +233,9 @@
     }
 
     @Override
-    public boolean onHoverEvent(MotionEvent ev) {
-        // If we've received this, we've already done the necessary handling
-        // in onInterceptHoverEvent. Return true to consume the event.
-        return false;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        int action = ev.getAction();
-        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
-            if (mTouchCompleteListener != null) {
-                mTouchCompleteListener.onTouchComplete();
-            }
-            mTouchCompleteListener = null;
-        }
-
-        if (mActiveController != null) {
-            return mActiveController.onControllerTouchEvent(ev);
-        } else {
-            // In case no child view handled the touch event, we may not get onIntercept anymore
-            return findActiveController(ev);
-        }
-    }
-
-    /**
-     * Determine the rect of the descendant in this DragLayer's coordinates
-     *
-     * @param descendant The descendant whose coordinates we want to find.
-     * @param r The rect into which to place the results.
-     * @return The factor by which this descendant is scaled relative to this DragLayer.
-     */
-    public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
-        mTmpXY[0] = 0;
-        mTmpXY[1] = 0;
-        float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
-
-        r.set(mTmpXY[0], mTmpXY[1],
-                (int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()),
-                (int) (mTmpXY[1] + scale * descendant.getMeasuredHeight()));
-        return scale;
-    }
-
-    public float getLocationInDragLayer(View child, int[] loc) {
-        loc[0] = 0;
-        loc[1] = 0;
-        return getDescendantCoordRelativeToSelf(child, loc);
-    }
-
-    public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
-        return getDescendantCoordRelativeToSelf(descendant, coord, false);
-    }
-
-    /**
-     * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
-     * coordinates.
-     *
-     * @param descendant The descendant to which the passed coordinate is relative.
-     * @param coord The coordinate that we want mapped.
-     * @param includeRootScroll Whether or not to account for the scroll of the root descendant:
-     *          sometimes this is relevant as in a child's coordinates within the root descendant.
-     * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
-     *         this scale factor is assumed to be equal in X and Y, and so if at any point this
-     *         assumption fails, we will need to return a pair of scale factors.
-     */
-    public float getDescendantCoordRelativeToSelf(View descendant, int[] coord,
-            boolean includeRootScroll) {
-        return Utilities.getDescendantCoordRelativeToAncestor(descendant, this,
-                coord, includeRootScroll);
-    }
-
-    /**
-     * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
-     */
-    public void mapCoordInSelfToDescendant(View descendant, int[] coord) {
-        Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
-    }
-
-    public void getViewRectRelativeToSelf(View v, Rect r) {
-        int[] loc = new int[2];
-        getLocationInWindow(loc);
-        int x = loc[0];
-        int y = loc[1];
-
-        v.getLocationInWindow(loc);
-        int vX = loc[0];
-        int vY = loc[1];
-
-        int left = vX - x;
-        int top = vY - y;
-        r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
-    }
-
-    @Override
     public boolean dispatchUnhandledMove(View focused, int direction) {
-        // Consume the unhandled move if a container is open, to avoid switching pages underneath.
-        boolean isContainerOpen = AbstractFloatingView.getTopOpenView(mLauncher) != null;
-        return isContainerOpen || mDragController.dispatchUnhandledMove(focused, direction);
+        return super.dispatchUnhandledMove(focused, direction)
+                || mDragController.dispatchUnhandledMove(focused, direction);
     }
 
     @Override
@@ -386,91 +248,6 @@
         }
     }
 
-    @Override
-    public LayoutParams generateLayoutParams(AttributeSet attrs) {
-        return new LayoutParams(getContext(), attrs);
-    }
-
-    @Override
-    protected LayoutParams generateDefaultLayoutParams() {
-        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
-    }
-
-    // Override to allow type-checking of LayoutParams.
-    @Override
-    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
-        return p instanceof LayoutParams;
-    }
-
-    @Override
-    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        return new LayoutParams(p);
-    }
-
-    public static class LayoutParams extends InsettableFrameLayout.LayoutParams {
-        public int x, y;
-        public boolean customPosition = false;
-
-        public LayoutParams(Context c, AttributeSet attrs) {
-            super(c, attrs);
-        }
-
-        public LayoutParams(int width, int height) {
-            super(width, height);
-        }
-
-        public LayoutParams(ViewGroup.LayoutParams lp) {
-            super(lp);
-        }
-
-        public void setWidth(int width) {
-            this.width = width;
-        }
-
-        public int getWidth() {
-            return width;
-        }
-
-        public void setHeight(int height) {
-            this.height = height;
-        }
-
-        public int getHeight() {
-            return height;
-        }
-
-        public void setX(int x) {
-            this.x = x;
-        }
-
-        public int getX() {
-            return x;
-        }
-
-        public void setY(int y) {
-            this.y = y;
-        }
-
-        public int getY() {
-            return y;
-        }
-    }
-
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        super.onLayout(changed, l, t, r, b);
-        int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            View child = getChildAt(i);
-            final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
-            if (flp instanceof LayoutParams) {
-                final LayoutParams lp = (LayoutParams) flp;
-                if (lp.customPosition) {
-                    child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
-                }
-            }
-        }
-    }
-
     public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
             float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
             int duration) {
@@ -709,14 +486,14 @@
     public void onViewAdded(View child) {
         super.onViewAdded(child);
         updateChildIndices();
-        UiFactory.onLauncherStateOrFocusChanged(mLauncher);
+        UiFactory.onLauncherStateOrFocusChanged(mActivity);
     }
 
     @Override
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
         updateChildIndices();
-        UiFactory.onLauncherStateOrFocusChanged(mLauncher);
+        UiFactory.onLauncherStateOrFocusChanged(mActivity);
     }
 
     @Override
@@ -768,32 +545,4 @@
         mFocusIndicatorHelper.draw(canvas);
         super.dispatchDraw(canvas);
     }
-
-    @Override
-    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
-        View topView = AbstractFloatingView.getTopOpenView(mLauncher);
-        if (topView != null) {
-            return topView.requestFocus(direction, previouslyFocusedRect);
-        } else {
-            return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
-        }
-    }
-
-    @Override
-    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
-        View topView = AbstractFloatingView.getTopOpenView(mLauncher);
-        if (topView != null) {
-            topView.addFocusables(views, direction);
-        } else {
-            super.addFocusables(views, direction, focusableMode);
-        }
-    }
-
-    public void setTouchCompleteListener(TouchCompleteListener listener) {
-        mTouchCompleteListener = listener;
-    }
-
-    public interface TouchCompleteListener {
-        void onTouchComplete();
-    }
 }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 2b42429..1bdd554 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -207,11 +207,11 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mContent = (FolderPagedView) findViewById(R.id.folder_content);
+        mContent = findViewById(R.id.folder_content);
         mContent.setFolder(this);
 
-        mPageIndicator = (PageIndicatorDots) findViewById(R.id.folder_page_indicator);
-        mFolderName = (ExtendedEditText) findViewById(R.id.folder_name);
+        mPageIndicator = findViewById(R.id.folder_page_indicator);
+        mFolderName = findViewById(R.id.folder_name);
         mFolderName.setOnBackKeyListener(this);
         mFolderName.setOnFocusChangeListener(this);
 
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 6c94273..cb5d872 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -568,7 +568,7 @@
     @Override
     public void onAdd(ShortcutInfo item, int rank) {
         boolean wasBadged = mBadgeInfo.hasBadge();
-        mBadgeInfo.addBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item));
+        mBadgeInfo.addBadgeInfo(mLauncher.getBadgeInfoForItem(item));
         boolean isBadged = mBadgeInfo.hasBadge();
         updateBadgeScale(wasBadged, isBadged);
         invalidate();
@@ -578,7 +578,7 @@
     @Override
     public void onRemove(ShortcutInfo item) {
         boolean wasBadged = mBadgeInfo.hasBadge();
-        mBadgeInfo.subtractBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item));
+        mBadgeInfo.subtractBadgeInfo(mLauncher.getBadgeInfoForItem(item));
         boolean isBadged = mBadgeInfo.hasBadge();
         updateBadgeScale(wasBadged, isBadged);
         invalidate();
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index 3393469..34a4e2d 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.graphics;
 
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -70,6 +71,10 @@
         return drawable;
     }
 
+    public FastBitmapDrawable newIcon(BitmapInfo info, ActivityInfo target) {
+        return new FastBitmapDrawable(info);
+    }
+
     /**
      * Returns a FastBitmapDrawable with the icon.
      */
@@ -80,7 +85,6 @@
         return new PreloadIconDrawable(info, mPreloadProgressPath, context);
     }
 
-
     protected Path getPreloadProgressPath(Context context) {
         if (Utilities.ATLEAST_OREO) {
             try {
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 4a9cdd9..3b5585b 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -29,11 +29,13 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.PaintDrawable;
 import android.os.Build;
@@ -60,6 +62,8 @@
  */
 public class LauncherIcons implements AutoCloseable {
 
+    private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
+
     public static final Object sPoolSync = new Object();
     private static LauncherIcons sPool;
 
@@ -84,6 +88,9 @@
      */
     public void recycle() {
         synchronized (sPoolSync) {
+            // Clear any temporary state variables
+            mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
+
             next = sPool;
             sPool = this;
         }
@@ -105,6 +112,9 @@
     private IconNormalizer mNormalizer;
     private ShadowGenerator mShadowGenerator;
 
+    private Drawable mWrapperIcon;
+    private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
+
     // sometimes we store linked lists of these things
     private LauncherIcons next;
 
@@ -172,6 +182,16 @@
      * The bitmap is also visually normalized with other icons.
      */
     public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk) {
+        return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
+    }
+
+    /**
+     * Returns a bitmap suitable for displaying as an icon at various launcher UIs like all apps
+     * view or workspace. The icon is badged for {@param user}.
+     * The bitmap is also visually normalized with other icons.
+     */
+    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk,
+            boolean isInstantApp) {
         float[] scale = new float[1];
         icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, null, scale);
         Bitmap bitmap = createIconBitmap(icon, scale[0]);
@@ -190,6 +210,9 @@
             } else {
                 result = createIconBitmap(badged, 1f);
             }
+        } else if (isInstantApp) {
+            badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
+            result = bitmap;
         } else {
             result = bitmap;
         }
@@ -208,13 +231,23 @@
                 Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
     }
 
+    /**
+     * Sets the background color used for wrapped adaptive icon
+     */
+    public void setWrapperBackgroundColor(int color) {
+        mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
+    }
+
     private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, int iconAppTargetSdk,
             RectF outIconBounds, float[] outScale) {
         float scale = 1f;
         if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
             boolean[] outShape = new boolean[1];
-            AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
-                    mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
+            if (mWrapperIcon == null) {
+                mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
+                        .mutate();
+            }
+            AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
             dr.setBounds(0, 0, 1, 1);
             scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
             if (Utilities.ATLEAST_OREO && !outShape[0] && !(icon instanceof AdaptiveIconDrawable)) {
@@ -223,6 +256,8 @@
                 fsd.setScale(scale);
                 icon = dr;
                 scale = getNormalizer().getScale(icon, outIconBounds, null, null);
+
+                ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
             }
         } else {
             scale = getNormalizer().getScale(icon, outIconBounds, null, null);
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index cbdabf3..1fd7078 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -74,6 +74,9 @@
     /** Maps keys to their corresponding current group key */
     private final Map<String, String> mNotificationGroupKeyMap = new HashMap<>();
 
+    /** The last notification key that was dismissed from launcher UI */
+    private String mLastKeyDismissedByLauncher;
+
     private SettingsObserver mNotificationBadgingObserver;
 
     private final Handler.Callback mWorkerCallback = new Handler.Callback() {
@@ -251,13 +254,25 @@
         }
 
         NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey());
+        String key = sbn.getKey();
         if (notificationGroup != null) {
-            notificationGroup.removeChildKey(sbn.getKey());
+            notificationGroup.removeChildKey(key);
             if (notificationGroup.isEmpty()) {
-                cancelNotification(notificationGroup.getGroupSummaryKey());
+                if (key.equals(mLastKeyDismissedByLauncher)) {
+                    // Only cancel the group notification if launcher dismissed the last child.
+                    cancelNotification(notificationGroup.getGroupSummaryKey());
+                }
                 mNotificationGroupMap.remove(sbn.getGroupKey());
             }
         }
+        if (key.equals(mLastKeyDismissedByLauncher)) {
+            mLastKeyDismissedByLauncher = null;
+        }
+    }
+
+    public void cancelNotificationFromLauncher(String key) {
+        mLastKeyDismissedByLauncher = key;
+        cancelNotification(key);
     }
 
     @Override
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index e427a81..033fdf8 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -745,7 +745,7 @@
 
     private void updateNotificationHeader() {
         ItemInfoWithIcon itemInfo = (ItemInfoWithIcon) mOriginalIcon.getTag();
-        BadgeInfo badgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo);
+        BadgeInfo badgeInfo = mLauncher.getBadgeInfoForItem(itemInfo);
         if (mNotificationItemView != null && badgeInfo != null) {
             mNotificationItemView.updateHeader(
                     badgeInfo.getNotificationCount(), itemInfo.iconColor);
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 335426c..f1b8ec0 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -206,7 +206,7 @@
         if (notificationListener == null) {
             return;
         }
-        notificationListener.cancelNotification(notificationKey);
+        notificationListener.cancelNotificationFromLauncher(notificationKey);
     }
 
     public void setAllWidgets(ArrayList<WidgetListRowEntry> allWidgets) {
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 2cc8dfa..a20149e 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -9,6 +9,7 @@
 import android.view.View;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
@@ -27,7 +28,7 @@
  *
  * Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
  */
-public abstract class SystemShortcut extends ItemInfo {
+public abstract class SystemShortcut<T extends BaseDraggingActivity> extends ItemInfo {
     public final int iconResId;
     public final int labelResId;
 
@@ -36,10 +37,9 @@
         this.labelResId = labelResId;
     }
 
-    public abstract View.OnClickListener getOnClickListener(final Launcher launcher,
-            final ItemInfo itemInfo);
+    public abstract View.OnClickListener getOnClickListener(T activity, ItemInfo itemInfo);
 
-    public static class Widgets extends SystemShortcut {
+    public static class Widgets extends SystemShortcut<Launcher> {
 
         public Widgets() {
             super(R.drawable.ic_widget, R.string.widget_button_text);
@@ -54,17 +54,14 @@
             if (widgets == null) {
                 return null;
             }
-            return new View.OnClickListener() {
-                @Override
-                public void onClick(View view) {
-                    AbstractFloatingView.closeAllOpenViews(launcher);
-                    WidgetsBottomSheet widgetsBottomSheet =
-                            (WidgetsBottomSheet) launcher.getLayoutInflater().inflate(
-                                    R.layout.widgets_bottom_sheet, launcher.getDragLayer(), false);
-                    widgetsBottomSheet.populateAndShow(itemInfo);
-                    launcher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
-                            ControlType.WIDGETS_BUTTON, view);
-                }
+            return (view) -> {
+                AbstractFloatingView.closeAllOpenViews(launcher);
+                WidgetsBottomSheet widgetsBottomSheet =
+                        (WidgetsBottomSheet) launcher.getLayoutInflater().inflate(
+                                R.layout.widgets_bottom_sheet, launcher.getDragLayer(), false);
+                widgetsBottomSheet.populateAndShow(itemInfo);
+                launcher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
+                        ControlType.WIDGETS_BUTTON, view);
             };
         }
     }
@@ -75,18 +72,15 @@
         }
 
         @Override
-        public View.OnClickListener getOnClickListener(final Launcher launcher,
-                final ItemInfo itemInfo) {
-            return new View.OnClickListener() {
-                @Override
-                public void onClick(View view) {
-                    Rect sourceBounds = launcher.getViewBounds(view);
-                    Bundle opts = launcher.getActivityLaunchOptionsAsBundle(view, false);
-                    new PackageManagerHelper(launcher).startDetailsActivityForInfo(
-                            itemInfo, sourceBounds, opts);
-                    launcher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
-                            ControlType.APPINFO_TARGET, view);
-                }
+        public View.OnClickListener getOnClickListener(
+                BaseDraggingActivity activity, ItemInfo itemInfo) {
+            return (view) -> {
+                Rect sourceBounds = activity.getViewBounds(view);
+                Bundle opts = activity.getActivityLaunchOptionsAsBundle(view, false);
+                new PackageManagerHelper(activity).startDetailsActivityForInfo(
+                        itemInfo, sourceBounds, opts);
+                activity.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
+                        ControlType.APPINFO_TARGET, view);
             };
         }
     }
@@ -97,28 +91,29 @@
         }
 
         @Override
-        public View.OnClickListener getOnClickListener(final Launcher launcher,
-                final ItemInfo itemInfo) {
+        public View.OnClickListener getOnClickListener(
+                BaseDraggingActivity activity, ItemInfo itemInfo) {
             boolean supportsWebUI = (itemInfo instanceof ShortcutInfo) &&
                     ((ShortcutInfo) itemInfo).hasStatusFlag(ShortcutInfo.FLAG_SUPPORTS_WEB_UI);
             boolean isInstantApp = false;
             if (itemInfo instanceof com.android.launcher3.AppInfo) {
                 com.android.launcher3.AppInfo appInfo = (com.android.launcher3.AppInfo) itemInfo;
-                isInstantApp = InstantAppResolver.newInstance(launcher).isInstantApp(appInfo);
+                isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo);
             }
             boolean enabled = supportsWebUI || isInstantApp;
             if (!enabled) {
                 return null;
             }
-            return createOnClickListener(launcher, itemInfo);
+            return createOnClickListener(activity, itemInfo);
         }
 
-        public View.OnClickListener createOnClickListener(Launcher launcher, ItemInfo itemInfo) {
+        public View.OnClickListener createOnClickListener(
+                BaseDraggingActivity activity, ItemInfo itemInfo) {
             return view -> {
                 Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent(
                         itemInfo.getTargetComponent().getPackageName());
-                launcher.startActivitySafely(view, intent, itemInfo);
-                AbstractFloatingView.closeAllOpenViews(launcher);
+                activity.startActivitySafely(view, intent, itemInfo);
+                AbstractFloatingView.closeAllOpenViews(activity);
             };
         }
     }
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
index d3c0fef..0a2c3e4 100644
--- a/src/com/android/launcher3/states/InternalStateHandler.java
+++ b/src/com/android/launcher3/states/InternalStateHandler.java
@@ -56,8 +56,8 @@
         sScheduler.schedule(this);
     }
 
-    public void clearReference() {
-        sScheduler.clearReference(this);
+    public boolean clearReference() {
+        return sScheduler.clearReference(this);
     }
 
     public static boolean handleCreate(Launcher launcher, Intent intent) {
@@ -125,10 +125,12 @@
             return false;
         }
 
-        public synchronized void clearReference(InternalStateHandler handler) {
+        public synchronized boolean clearReference(InternalStateHandler handler) {
             if (mPendingHandler.get() == handler) {
                 mPendingHandler.clear();
+                return true;
             }
+            return false;
         }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
new file mode 100644
index 0000000..db53634
--- /dev/null
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.touch;
+
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.TouchController;
+
+/**
+ * TouchController for handling state changes
+ */
+public abstract class AbstractStateChangeTouchController extends AnimatorListenerAdapter
+        implements TouchController, SwipeDetector.Listener {
+
+    private static final String TAG = "ASCTouchController";
+    public static final float RECATCH_REJECTION_FRACTION = .0875f;
+    public static final int SINGLE_FRAME_MS = 16;
+
+    // Progress after which the transition is assumed to be a success in case user does not fling
+    public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
+
+    protected final Launcher mLauncher;
+    protected final SwipeDetector mDetector;
+
+    private boolean mNoIntercept;
+    protected int mStartContainerType;
+
+    protected LauncherState mFromState;
+    protected LauncherState mToState;
+    protected AnimatorPlaybackController mCurrentAnimation;
+
+    private float mStartProgress;
+    // Ratio of transition process [0, 1] to drag displacement (px)
+    private float mProgressMultiplier;
+
+    public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
+        mLauncher = l;
+        mDetector = new SwipeDetector(l, this, dir);
+    }
+
+    protected abstract boolean canInterceptTouch(MotionEvent ev);
+
+    /**
+     * Initializes the {@code mFromState} and {@code mToState} and swipe direction to use for
+     * the detector. In can of disabling swipe, return 0.
+     */
+    protected abstract int getSwipeDirection(MotionEvent ev);
+
+    @Override
+    public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mNoIntercept = !canInterceptTouch(ev);
+            if (mNoIntercept) {
+                return false;
+            }
+
+            // Now figure out which direction scroll events the controller will start
+            // calling the callbacks.
+            final int directionsToDetectScroll;
+            boolean ignoreSlopWhenSettling = false;
+
+            if (mCurrentAnimation != null) {
+                if (mCurrentAnimation.getProgressFraction() > 1 - RECATCH_REJECTION_FRACTION) {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+                } else if (mCurrentAnimation.getProgressFraction() < RECATCH_REJECTION_FRACTION ) {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
+                } else {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+                    ignoreSlopWhenSettling = true;
+                }
+            } else {
+                directionsToDetectScroll = getSwipeDirection(ev);
+                if (directionsToDetectScroll == 0) {
+                    mNoIntercept = true;
+                    return false;
+                }
+            }
+            mDetector.setDetectableScrollConditions(
+                    directionsToDetectScroll, ignoreSlopWhenSettling);
+        }
+
+        if (mNoIntercept) {
+            return false;
+        }
+
+        onControllerTouchEvent(ev);
+        return mDetector.isDraggingOrSettling();
+    }
+
+    @Override
+    public final boolean onControllerTouchEvent(MotionEvent ev) {
+        return mDetector.onTouchEvent(ev);
+    }
+
+    protected float getShiftRange() {
+        return mLauncher.getAllAppsController().getShiftRange();
+    }
+
+    protected abstract float initCurrentAnimation();
+
+    @Override
+    public void onDragStart(boolean start) {
+        if (mCurrentAnimation == null) {
+            mStartProgress = 0;
+            mProgressMultiplier = initCurrentAnimation();
+
+            mCurrentAnimation.getTarget().addListener(this);
+            mCurrentAnimation.dispatchOnStart();
+        } else {
+            mCurrentAnimation.pause();
+            mStartProgress = mCurrentAnimation.getProgressFraction();
+        }
+    }
+
+    @Override
+    public boolean onDrag(float displacement, float velocity) {
+        float deltaProgress = mProgressMultiplier * displacement;
+        mCurrentAnimation.setPlayFraction(deltaProgress + mStartProgress);
+        return true;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        final int logAction;
+        final LauncherState targetState;
+        final float progress = mCurrentAnimation.getProgressFraction();
+
+        if (fling) {
+            logAction = Touch.FLING;
+            targetState =
+                    Float.compare(Math.signum(velocity), Math.signum(mProgressMultiplier)) == 0
+                            ? mToState : mFromState;
+            // snap to top or bottom using the release velocity
+        } else {
+            logAction = Touch.SWIPE;
+            targetState = (progress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
+        }
+
+
+        final float endProgress;
+        final float startProgress;
+        final long duration;
+
+        if (targetState == mToState) {
+            endProgress = 1;
+            if (progress >= 1) {
+                duration = 0;
+                startProgress = 1;
+            } else {
+                startProgress = Utilities.boundToRange(
+                        progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
+                duration = SwipeDetector.calculateDuration(velocity,
+                        endProgress - Math.max(progress, 0));
+            }
+        } else {
+            endProgress = 0;
+            if (progress <= 0) {
+                duration = 0;
+                startProgress = 0;
+            } else {
+                startProgress = Utilities.boundToRange(
+                        progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
+                duration = SwipeDetector.calculateDuration(velocity,
+                        Math.min(progress, 1) - endProgress);
+            }
+        }
+
+        mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState, logAction));
+        ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
+        anim.setFloatValues(startProgress, endProgress);
+        anim.setDuration(duration).setInterpolator(scrollInterpolatorForVelocity(velocity));
+        anim.start();
+    }
+
+    protected int getDirectionForLog() {
+        return mToState.ordinal > mFromState.ordinal ? Direction.UP : Direction.DOWN;
+    }
+
+    protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
+        if (targetState != mFromState) {
+            // Transition complete. log the action
+            mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
+                    getDirectionForLog(),
+                    mStartContainerType,
+                    mFromState.containerType,
+                    mToState.containerType,
+                    mLauncher.getWorkspace().getCurrentPage());
+        }
+        clearState();
+        mLauncher.getStateManager().goToState(targetState, false /* animated */);
+    }
+
+    protected void clearState() {
+        mCurrentAnimation = null;
+        mDetector.finishedScrolling();
+    }
+
+    @Override
+    public void onAnimationCancel(Animator animation) {
+        if (mCurrentAnimation != null && animation == mCurrentAnimation.getOriginalTarget()) {
+            Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
+            clearState();
+        }
+    }
+}
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index f10a695..6f012f6 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -18,6 +18,7 @@
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
+import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 
@@ -78,8 +79,8 @@
         Launcher launcher = Launcher.getLauncher(v.getContext());
         if (!canStartDrag(launcher)) return false;
         // When we have exited all apps or are in transition, disregard long clicks
-        if (!launcher.isInState(LauncherState.ALL_APPS) ||
-                launcher.getWorkspace().isSwitchingState()) return false;
+        if (!launcher.isInState(ALL_APPS) && !launcher.isInState(OVERVIEW)) return false;
+        if (launcher.getWorkspace().isSwitchingState()) return false;
 
         // Start the drag
         final DragController dragController = launcher.getDragController();
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
new file mode 100644
index 0000000..df11686
--- /dev/null
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -0,0 +1,143 @@
+/*
+ * 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.touch;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.ViewConfiguration.getLongPressTimeout;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.views.OptionsPopupView;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+
+/**
+ * Helper class to handle touch on empty space in workspace and show options popup on long press
+ */
+public class WorkspaceTouchListener implements OnTouchListener, Runnable {
+
+    /**
+     * STATE_PENDING_PARENT_INFORM is the state between longPress performed & the next motionEvent.
+     * This next event is used to send an ACTION_CANCEL to Workspace, to that it clears any
+     * temporary scroll state. After that, the state is set to COMPLETED, and we just eat up all
+     * subsequent motion events.
+     */
+    private static final int STATE_CANCELLED = 0;
+    private static final int STATE_REQUESTED = 1;
+    private static final int STATE_PENDING_PARENT_INFORM = 2;
+    private static final int STATE_COMPLETED = 3;
+
+    private final Rect mTempRect = new Rect();
+    private final Launcher mLauncher;
+    private final Workspace mWorkspace;
+    private final PointF mTouchDownPoint = new PointF();
+
+    private int mLongPressState = STATE_CANCELLED;
+
+    public WorkspaceTouchListener(Launcher launcher, Workspace workspace) {
+        mLauncher = launcher;
+        mWorkspace = workspace;
+    }
+
+    @Override
+    public boolean onTouch(View view, MotionEvent ev) {
+        int action = ev.getActionMasked();
+        if (action == ACTION_DOWN) {
+            // Check if we can handle long press.
+            boolean handleLongPress = AbstractFloatingView.getTopOpenView(mLauncher) == null
+                    && mLauncher.isInState(NORMAL);
+
+            if (handleLongPress) {
+                // Check if the event is not near the edges
+                DeviceProfile dp = mLauncher.getDeviceProfile();
+                DragLayer dl = mLauncher.getDragLayer();
+                Rect insets = dp.getInsets();
+
+                mTempRect.set(insets.left, insets.top, dl.getWidth() - insets.right,
+                        dl.getHeight() - insets.bottom);
+                mTempRect.inset(dp.edgeMarginPx, dp.edgeMarginPx);
+                handleLongPress = mTempRect.contains((int) ev.getX(), (int) ev.getY());
+            }
+
+            cancelLongPress();
+            if (handleLongPress) {
+                mLongPressState = STATE_REQUESTED;
+                mTouchDownPoint.set(ev.getX(), ev.getY());
+                mWorkspace.postDelayed(this, getLongPressTimeout());
+            }
+
+            mWorkspace.onTouchEvent(ev);
+            // Return true to keep receiving touch events
+            return true;
+        }
+
+        if (mLongPressState == STATE_PENDING_PARENT_INFORM) {
+            // Inform the workspace to cancel touch handling
+            ev.setAction(ACTION_CANCEL);
+            mWorkspace.onTouchEvent(ev);
+            ev.setAction(action);
+            mLongPressState = STATE_COMPLETED;
+        }
+
+        if (mLongPressState == STATE_COMPLETED) {
+            // We have handled the touch, so workspace does not need to know anything anymore.
+            return true;
+        } else if (mLongPressState == STATE_REQUESTED) {
+            mWorkspace.onTouchEvent(ev);
+            if (action == ACTION_UP || action == ACTION_CANCEL || mWorkspace.isHandlingTouch()) {
+                cancelLongPress();
+            }
+            return true;
+        } else {
+            // We don't want to handle touch, let workspace handle it as usual.
+            return false;
+        }
+    }
+
+    private void cancelLongPress() {
+        mWorkspace.removeCallbacks(this);
+        mLongPressState = STATE_CANCELLED;
+    }
+
+    @Override
+    public void run() {
+        if (mLongPressState == STATE_REQUESTED) {
+            mLongPressState = STATE_PENDING_PARENT_INFORM;
+            mWorkspace.getParent().requestDisallowInterceptTouchEvent(true);
+
+            mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+            mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
+                    Action.Direction.NONE, ContainerType.WORKSPACE,
+                    mWorkspace.getCurrentPage());
+            OptionsPopupView.show(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/InstantAppResolver.java b/src/com/android/launcher3/util/InstantAppResolver.java
index 601a5ab..4485427 100644
--- a/src/com/android/launcher3/util/InstantAppResolver.java
+++ b/src/com/android/launcher3/util/InstantAppResolver.java
@@ -22,7 +22,6 @@
 import android.util.Log;
 
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 
@@ -47,8 +46,8 @@
         return false;
     }
 
-    public boolean isInstantApp(Launcher launcher, String packageName) {
-        PackageManager packageManager = launcher.getPackageManager();
+    public boolean isInstantApp(Context context, String packageName) {
+        PackageManager packageManager = context.getPackageManager();
         try {
             return isInstantApp(packageManager.getPackageInfo(packageName, 0).applicationInfo);
         } catch (PackageManager.NameNotFoundException e) {
diff --git a/src/com/android/launcher3/util/VerticalSwipeController.java b/src/com/android/launcher3/util/VerticalSwipeController.java
deleted file mode 100644
index ae5bfd5..0000000
--- a/src/com/android/launcher3/util/VerticalSwipeController.java
+++ /dev/null
@@ -1,224 +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.launcher3.util;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.touch.SwipeDetector;
-import com.android.launcher3.touch.SwipeDetector.Direction;
-
-
-/**
- * Handles vertical touch gesture on the DragLayer allowing transitioning from
- * {@link #mBaseState} to {@link LauncherState#ALL_APPS} and vice-versa.
- */
-public abstract class VerticalSwipeController extends AnimatorListenerAdapter
-        implements TouchController, SwipeDetector.Listener {
-
-    private static final String TAG = "VerticalSwipeController";
-
-    private static final float RECATCH_REJECTION_FRACTION = .0875f;
-    private static final int SINGLE_FRAME_MS = 16;
-
-    // Progress after which the transition is assumed to be a success in case user does not fling
-    private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
-
-    protected final Launcher mLauncher;
-    protected final SwipeDetector mDetector;
-    private final LauncherState mBaseState;
-    private final LauncherState mTargetState;
-
-    private boolean mNoIntercept;
-
-    private AnimatorPlaybackController mCurrentAnimation;
-    protected LauncherState mToState;
-
-    private float mStartProgress;
-    // Ratio of transition process [0, 1] to drag displacement (px)
-    private float mProgressMultiplier;
-
-    public VerticalSwipeController(Launcher l, LauncherState baseState) {
-        this(l, baseState, ALL_APPS, SwipeDetector.VERTICAL);
-    }
-
-    public VerticalSwipeController(
-            Launcher l, LauncherState baseState, LauncherState targetState, Direction dir) {
-        mLauncher = l;
-        mDetector = new SwipeDetector(l, this, dir);
-        mBaseState = baseState;
-        mTargetState = targetState;
-    }
-
-    private boolean canInterceptTouch(MotionEvent ev) {
-        if (mCurrentAnimation != null) {
-            // If we are already animating from a previous state, we can intercept.
-            return true;
-        }
-        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
-            return false;
-        }
-        return shouldInterceptTouch(ev);
-    }
-
-    protected abstract boolean shouldInterceptTouch(MotionEvent ev);
-
-    @Override
-    public void onAnimationCancel(Animator animation) {
-        if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
-            Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
-            mDetector.finishedScrolling();
-            mCurrentAnimation = null;
-        }
-    }
-
-    @Override
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            mNoIntercept = !canInterceptTouch(ev);
-            if (mNoIntercept) {
-                return false;
-            }
-
-            // Now figure out which direction scroll events the controller will start
-            // calling the callbacks.
-            final int directionsToDetectScroll;
-            boolean ignoreSlopWhenSettling = false;
-
-            if (mCurrentAnimation != null) {
-                if (mCurrentAnimation.getProgressFraction() > 1 - RECATCH_REJECTION_FRACTION) {
-                    directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
-                } else if (mCurrentAnimation.getProgressFraction() < RECATCH_REJECTION_FRACTION ) {
-                    directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
-                } else {
-                    directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
-                    ignoreSlopWhenSettling = true;
-                }
-            } else {
-                directionsToDetectScroll = getSwipeDirection(ev);
-            }
-
-            mDetector.setDetectableScrollConditions(
-                    directionsToDetectScroll, ignoreSlopWhenSettling);
-        }
-
-        if (mNoIntercept) {
-            return false;
-        }
-
-        onControllerTouchEvent(ev);
-        return mDetector.isDraggingOrSettling();
-    }
-
-    protected abstract int getSwipeDirection(MotionEvent ev);
-
-    @Override
-    public boolean onControllerTouchEvent(MotionEvent ev) {
-        return mDetector.onTouchEvent(ev);
-    }
-
-    @Override
-    public void onDragStart(boolean start) {
-        if (mCurrentAnimation == null) {
-            float range = getShiftRange();
-            long maxAccuracy = (long) (2 * range);
-
-            // Build current animation
-            mToState = mLauncher.isInState(mTargetState) ? mBaseState : mTargetState;
-            mCurrentAnimation = mLauncher.getStateManager()
-                    .createAnimationToNewWorkspace(mToState, maxAccuracy);
-            mCurrentAnimation.getTarget().addListener(this);
-            mStartProgress = 0;
-            mProgressMultiplier =
-                    (mLauncher.isInState(mTargetState) ^ isTransitionFlipped() ? 1 : -1) / range;
-            mCurrentAnimation.dispatchOnStart();
-        } else {
-            mCurrentAnimation.pause();
-            mStartProgress = mCurrentAnimation.getProgressFraction();
-        }
-    }
-
-    protected boolean isTransitionFlipped() {
-        return false;
-    }
-
-    protected float getShiftRange() {
-        return mLauncher.getAllAppsController().getShiftRange();
-    }
-
-    @Override
-    public boolean onDrag(float displacement, float velocity) {
-        float deltaProgress = mProgressMultiplier * displacement;
-        mCurrentAnimation.setPlayFraction(deltaProgress + mStartProgress);
-        return true;
-    }
-
-    @Override
-    public void onDragEnd(float velocity, boolean fling) {
-        final long animationDuration;
-        final LauncherState targetState;
-        final float progress = mCurrentAnimation.getProgressFraction();
-
-        if (fling) {
-            if (velocity < 0 ^ isTransitionFlipped()) {
-                targetState = mTargetState;
-            } else {
-                targetState = mBaseState;
-            }
-            animationDuration = SwipeDetector.calculateDuration(velocity,
-                    mToState == targetState ? (1 - progress) : progress);
-            // snap to top or bottom using the release velocity
-        } else {
-            if (progress > SUCCESS_TRANSITION_PROGRESS) {
-                targetState = mToState;
-                animationDuration = SwipeDetector.calculateDuration(velocity, 1 - progress);
-            } else {
-                targetState = mToState == mTargetState ? mBaseState : mTargetState;
-                animationDuration = SwipeDetector.calculateDuration(velocity, progress);
-            }
-        }
-
-        mCurrentAnimation.setEndAction(() -> {
-            mLauncher.getStateManager().goToState(targetState, false);
-            onTransitionComplete(fling, targetState == mToState);
-            mDetector.finishedScrolling();
-            mCurrentAnimation = null;
-        });
-
-        float nextFrameProgress = Utilities.boundToRange(
-                progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
-
-        ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
-        anim.setFloatValues(nextFrameProgress, targetState == mToState ? 1f : 0f);
-        anim.setDuration(animationDuration);
-        anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
-        anim.start();
-    }
-
-    protected abstract void onTransitionComplete(boolean wasFling, boolean stateChanged);
-}
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
new file mode 100644
index 0000000..489e59e
--- /dev/null
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -0,0 +1,325 @@
+/*
+ * 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.views;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.TouchController;
+
+import java.util.ArrayList;
+
+/**
+ * A viewgroup with utility methods for drag-n-drop and touch interception
+ */
+public abstract class BaseDragLayer<T extends BaseDraggingActivity> extends InsettableFrameLayout {
+
+    protected final int[] mTmpXY = new int[2];
+    protected final Rect mHitRect = new Rect();
+
+    protected final T mActivity;
+
+    protected TouchController[] mControllers;
+    protected TouchController mActiveController;
+    private TouchCompleteListener mTouchCompleteListener;
+
+    public BaseDragLayer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mActivity = (T) BaseActivity.fromContext(context);
+    }
+
+
+    public boolean isEventOverView(View view, MotionEvent ev) {
+        getDescendantRectRelativeToSelf(view, mHitRect);
+        return mHitRect.contains((int) ev.getX(), (int) ev.getY());
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        int action = ev.getAction();
+
+        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+            if (mTouchCompleteListener != null) {
+                mTouchCompleteListener.onTouchComplete();
+            }
+            mTouchCompleteListener = null;
+        } else if (action == MotionEvent.ACTION_DOWN) {
+            mActivity.finishAutoCancelActionMode();
+        }
+        return findActiveController(ev);
+    }
+
+    protected boolean findActiveController(MotionEvent ev) {
+        mActiveController = null;
+
+        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
+        if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
+            mActiveController = topView;
+            return true;
+        }
+
+        for (TouchController controller : mControllers) {
+            if (controller.onControllerInterceptTouchEvent(ev)) {
+                mActiveController = controller;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        // Shortcuts can appear above folder
+        View topView = AbstractFloatingView.getTopOpenView(mActivity);
+        if (topView != null) {
+            if (child == topView) {
+                return super.onRequestSendAccessibilityEvent(child, event);
+            }
+            // Skip propagating onRequestSendAccessibilityEvent for all other children
+            // which are not topView
+            return false;
+        }
+        return super.onRequestSendAccessibilityEvent(child, event);
+    }
+
+    @Override
+    public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
+        View topView = AbstractFloatingView.getTopOpenView(mActivity);
+        if (topView != null) {
+            // Only add the top view as a child for accessibility when it is open
+            childrenForAccessibility.add(topView);
+        } else {
+            super.addChildrenForAccessibility(childrenForAccessibility);
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        int action = ev.getAction();
+        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+            if (mTouchCompleteListener != null) {
+                mTouchCompleteListener.onTouchComplete();
+            }
+            mTouchCompleteListener = null;
+        }
+
+        if (mActiveController != null) {
+            return mActiveController.onControllerTouchEvent(ev);
+        } else {
+            // In case no child view handled the touch event, we may not get onIntercept anymore
+            return findActiveController(ev);
+        }
+    }
+
+    /**
+     * Determine the rect of the descendant in this DragLayer's coordinates
+     *
+     * @param descendant The descendant whose coordinates we want to find.
+     * @param r The rect into which to place the results.
+     * @return The factor by which this descendant is scaled relative to this DragLayer.
+     */
+    public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
+        mTmpXY[0] = 0;
+        mTmpXY[1] = 0;
+        float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
+
+        r.set(mTmpXY[0], mTmpXY[1],
+                (int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()),
+                (int) (mTmpXY[1] + scale * descendant.getMeasuredHeight()));
+        return scale;
+    }
+
+    public float getLocationInDragLayer(View child, int[] loc) {
+        loc[0] = 0;
+        loc[1] = 0;
+        return getDescendantCoordRelativeToSelf(child, loc);
+    }
+
+    public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
+        return getDescendantCoordRelativeToSelf(descendant, coord, false);
+    }
+
+    /**
+     * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
+     * coordinates.
+     *
+     * @param descendant The descendant to which the passed coordinate is relative.
+     * @param coord The coordinate that we want mapped.
+     * @param includeRootScroll Whether or not to account for the scroll of the root descendant:
+     *          sometimes this is relevant as in a child's coordinates within the root descendant.
+     * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
+     *         this scale factor is assumed to be equal in X and Y, and so if at any point this
+     *         assumption fails, we will need to return a pair of scale factors.
+     */
+    public float getDescendantCoordRelativeToSelf(View descendant, int[] coord,
+            boolean includeRootScroll) {
+        return Utilities.getDescendantCoordRelativeToAncestor(descendant, this,
+                coord, includeRootScroll);
+    }
+
+    /**
+     * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
+     */
+    public void mapCoordInSelfToDescendant(View descendant, int[] coord) {
+        Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
+    }
+
+    public void getViewRectRelativeToSelf(View v, Rect r) {
+        int[] loc = new int[2];
+        getLocationInWindow(loc);
+        int x = loc[0];
+        int y = loc[1];
+
+        v.getLocationInWindow(loc);
+        int vX = loc[0];
+        int vY = loc[1];
+
+        int left = vX - x;
+        int top = vY - y;
+        r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
+    }
+
+    @Override
+    public boolean dispatchUnhandledMove(View focused, int direction) {
+        // Consume the unhandled move if a container is open, to avoid switching pages underneath.
+        return AbstractFloatingView.getTopOpenView(mActivity) != null;
+    }
+
+    @Override
+    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+        View topView = AbstractFloatingView.getTopOpenView(mActivity);
+        if (topView != null) {
+            return topView.requestFocus(direction, previouslyFocusedRect);
+        } else {
+            return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+        }
+    }
+
+    @Override
+    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+        View topView = AbstractFloatingView.getTopOpenView(mActivity);
+        if (topView != null) {
+            topView.addFocusables(views, direction);
+        } else {
+            super.addFocusables(views, direction, focusableMode);
+        }
+    }
+
+    public void setTouchCompleteListener(TouchCompleteListener listener) {
+        mTouchCompleteListener = listener;
+    }
+
+    public interface TouchCompleteListener {
+        void onTouchComplete();
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+    }
+
+    // Override to allow type-checking of LayoutParams.
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LayoutParams;
+    }
+
+    @Override
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new LayoutParams(p);
+    }
+
+    public static class LayoutParams extends InsettableFrameLayout.LayoutParams {
+        public int x, y;
+        public boolean customPosition = false;
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams lp) {
+            super(lp);
+        }
+
+        public void setWidth(int width) {
+            this.width = width;
+        }
+
+        public int getWidth() {
+            return width;
+        }
+
+        public void setHeight(int height) {
+            this.height = height;
+        }
+
+        public int getHeight() {
+            return height;
+        }
+
+        public void setX(int x) {
+            this.x = x;
+        }
+
+        public int getX() {
+            return x;
+        }
+
+        public void setY(int y) {
+            this.y = y;
+        }
+
+        public int getY() {
+            return y;
+        }
+    }
+
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
+            if (flp instanceof LayoutParams) {
+                final LayoutParams lp = (LayoutParams) flp;
+                if (lp.customPosition) {
+                    child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
+                }
+            }
+        }
+    }
+}
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));
         }
     }
 
diff --git a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
index 01b63be..a11a8c5 100644
--- a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
+++ b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
@@ -20,7 +20,6 @@
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Region;
 import android.support.v4.graphics.ColorUtils;
 import android.util.AttributeSet;
 import android.widget.TextView;
diff --git a/src/com/android/launcher3/views/HighlightableListView.java b/src/com/android/launcher3/views/HighlightableListView.java
new file mode 100644
index 0000000..7da979f
--- /dev/null
+++ b/src/com/android/launcher3/views/HighlightableListView.java
@@ -0,0 +1,138 @@
+/*
+ * 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.views;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.v4.graphics.ColorUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.HeaderViewListAdapter;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+
+import java.util.ArrayList;
+
+/**
+ * Extension of list view with support for element highlighting.
+ */
+public class HighlightableListView extends ListView {
+
+    private int mPosHighlight = -1;
+    private boolean mColorAnimated = false;
+
+    public HighlightableListView(Context context) {
+        super(context);
+    }
+
+    public HighlightableListView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public HighlightableListView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    public void setAdapter(ListAdapter adapter) {
+        super.setAdapter(new HighLightAdapter(adapter));
+    }
+
+    public void highlightPosition(int pos) {
+        if (mPosHighlight == pos) {
+            return;
+        }
+
+        mColorAnimated = false;
+        mPosHighlight = pos;
+        setSelection(mPosHighlight);
+
+        int start = getFirstVisiblePosition();
+        int end = getLastVisiblePosition();
+        if (start <= mPosHighlight && mPosHighlight <= end) {
+            highlightView(getChildAt(mPosHighlight - start));
+        }
+    }
+
+    private void highlightView(View view) {
+        if (Boolean.TRUE.equals(view.getTag(R.id.view_highlighted))) {
+            // already highlighted
+        } else {
+            view.setTag(R.id.view_highlighted, true);
+            view.setTag(R.id.view_unhighlight_background, view.getBackground());
+            view.setBackground(getHighlightBackground());
+            view.postDelayed(() -> {
+                mPosHighlight = -1;
+                unhighlightView(view);
+            }, 15000L);
+        }
+    }
+
+    private void unhighlightView(View view) {
+        if (Boolean.TRUE.equals(view.getTag(R.id.view_highlighted))) {
+            Object background = view.getTag(R.id.view_unhighlight_background);
+            if (background instanceof Drawable) {
+                view.setBackground((Drawable) background);
+            }
+            view.setTag(R.id.view_unhighlight_background, null);
+            view.setTag(R.id.view_highlighted, false);
+        }
+    }
+
+    private class HighLightAdapter extends HeaderViewListAdapter {
+        public HighLightAdapter(ListAdapter adapter) {
+            super(new ArrayList<>(), new ArrayList<>(), adapter);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View view =  super.getView(position, convertView, parent);
+
+            if (position == mPosHighlight) {
+                highlightView(view);
+            } else {
+                unhighlightView(view);
+            }
+            return view;
+        }
+    }
+
+    private ColorDrawable getHighlightBackground() {
+        int color = ColorUtils.setAlphaComponent(Themes.getColorAccent(getContext()), 26);
+        if (mColorAnimated) {
+            return new ColorDrawable(color);
+        }
+        mColorAnimated = true;
+        ColorDrawable bg = new ColorDrawable(Color.WHITE);
+        ObjectAnimator anim = ObjectAnimator.ofInt(bg, "color", Color.WHITE, color);
+        anim.setEvaluator(new ArgbEvaluator());
+        anim.setDuration(200L);
+        anim.setRepeatMode(ValueAnimator.REVERSE);
+        anim.setRepeatCount(4);
+        anim.start();
+        return bg;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
similarity index 81%
rename from quickstep/src/com/android/launcher3/uioverrides/OptionsPopupView.java
rename to src/com/android/launcher3/views/OptionsPopupView.java
index ccdcbed..21b6773 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.views;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -29,6 +29,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
 import android.widget.Toast;
@@ -43,12 +44,15 @@
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.graphics.ColorScrim;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.widget.WidgetsFullSheet;
 
 /**
  * Popup shown on long pressing an empty space in launcher
  */
-public class OptionsPopupView extends AbstractFloatingView implements OnClickListener {
+public class OptionsPopupView extends AbstractFloatingView
+        implements OnClickListener, OnLongClickListener {
 
     private final float mOutlineRadius;
     private final Launcher mLauncher;
@@ -81,29 +85,49 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        findViewById(R.id.wallpaper_button).setOnClickListener(this);
-        findViewById(R.id.widget_button).setOnClickListener(this);
-        findViewById(R.id.settings_button).setOnClickListener(this);
+        attachListeners(findViewById(R.id.wallpaper_button));
+        attachListeners(findViewById(R.id.widget_button));
+        attachListeners(findViewById(R.id.settings_button));
+    }
+
+    private void attachListeners(View view) {
+        view.setOnClickListener(this);
+        view.setOnLongClickListener(this);
     }
 
     @Override
     public void onClick(View view) {
+        handleViewClick(view, Action.Touch.TAP);
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        return handleViewClick(view, Action.Touch.LONGPRESS);
+    }
+
+    private boolean handleViewClick(View view, int action) {
         if (view.getId() == R.id.wallpaper_button) {
             mLauncher.onClickWallpaperPicker(null);
+            logTap(action, ControlType.WALLPAPER_BUTTON);
             close(true);
+            return true;
         } else if (view.getId() == R.id.widget_button) {
-            if (mLauncher.getPackageManager().isSafeMode()) {
-                Toast.makeText(mLauncher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
-            } else {
-                WidgetsFullSheet.show(mLauncher, true /* animated */);
+            logTap(action, ControlType.WIDGETS_BUTTON);
+            if (onWidgetsClicked(mLauncher)) {
                 close(true);
+                return true;
             }
         } else if (view.getId() == R.id.settings_button) {
-            mLauncher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
-                .setPackage(mLauncher.getPackageName())
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+            startSettings(mLauncher);
+            logTap(action, ControlType.SETTINGS_BUTTON);
             close(true);
+            return true;
         }
+        return false;
+    }
+
+    private void logTap(int action, int controlType) {
+        mLauncher.getUserEventDispatcher().logActionOnControl(action, controlType);
     }
 
     @Override
@@ -267,4 +291,20 @@
         launcher.getDragLayer().addView(view);
         view.animateOpen();
     }
+
+    public static boolean onWidgetsClicked(Launcher launcher) {
+        if (launcher.getPackageManager().isSafeMode()) {
+            Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
+            return false;
+        } else {
+            WidgetsFullSheet.show(launcher, true /* animated */);
+            return true;
+        }
+    }
+
+    public static void startSettings(Launcher launcher) {
+        launcher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
+                .setPackage(launcher.getPackageName())
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+    }
 }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 9d74218..12859c7 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -47,7 +47,7 @@
 import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.dragndrop.DragLayer.TouchCompleteListener;
+import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
 
 import java.util.ArrayList;
 
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
index 2acd29b..c05d30c 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
@@ -63,8 +63,8 @@
     }
 
     @Override
-    public float getHoseatAlpha(Launcher launcher) {
-        return 0;
+    public int getVisibleElements(Launcher launcher) {
+        return ALL_APPS_HEADER | ALL_APPS_CONTENT;
     }
 
     @Override
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
index 76b7e0d..e495477 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
@@ -1,19 +1,3 @@
-/*
- * 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.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -21,31 +5,34 @@
 
 import android.view.MotionEvent;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SwipeDetector;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.util.VerticalSwipeController;
 
 /**
- * Extension of {@link VerticalSwipeController} to switch between NORMAL and ALL_APPS state.
+ * TouchController to switch between NORMAL and ALL_APPS state.
  */
-public class AllAppsSwipeController extends VerticalSwipeController {
-
-    private int mStartContainerType;
+public class AllAppsSwipeController extends AbstractStateChangeTouchController {
 
     public AllAppsSwipeController(Launcher l) {
-        super(l, NORMAL);
+        super(l, SwipeDetector.VERTICAL);
     }
 
     @Override
-    protected boolean shouldInterceptTouch(MotionEvent ev) {
+    protected boolean canInterceptTouch(MotionEvent ev) {
+        if (mCurrentAnimation != null) {
+            // If we are already animating from a previous state, we can intercept.
+            return true;
+        }
+        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+            return false;
+        }
         if (!mLauncher.isInState(NORMAL) && !mLauncher.isInState(ALL_APPS)) {
             // Don't listen for the swipe gesture if we are already in some other state.
             return false;
         }
-
         if (mLauncher.isInState(ALL_APPS) && !mLauncher.getAppsView().shouldContainerScroll(ev)) {
             return false;
         }
@@ -56,8 +43,12 @@
     protected int getSwipeDirection(MotionEvent ev) {
         if (mLauncher.isInState(ALL_APPS)) {
             mStartContainerType = ContainerType.ALLAPPS;
+            mFromState = ALL_APPS;
+            mToState = NORMAL;
             return SwipeDetector.DIRECTION_NEGATIVE;
         } else {
+            mFromState = NORMAL;
+            mToState = ALL_APPS;
             mStartContainerType = mLauncher.getDragLayer().isEventOverHotseat(ev) ?
                     ContainerType.HOTSEAT : ContainerType.WORKSPACE;
             return SwipeDetector.DIRECTION_POSITIVE;
@@ -65,14 +56,14 @@
     }
 
     @Override
-    protected void onTransitionComplete(boolean wasFling, boolean stateChanged) {
-        if (stateChanged) {
-            // Transition complete. log the action
-            mLauncher.getUserEventDispatcher().logActionOnContainer(
-                    wasFling ? Touch.FLING : Touch.SWIPE,
-                    mLauncher.isInState(ALL_APPS) ? Direction.UP : Direction.DOWN,
-                    mStartContainerType,
-                    mLauncher.getWorkspace().getCurrentPage());
-        }
+    protected float initCurrentAnimation() {
+        float range = getShiftRange();
+        long maxAccuracy = (long) (2 * range);
+        mCurrentAnimation = mLauncher.getStateManager()
+                .createAnimationToNewWorkspace(mToState, maxAccuracy);
+        float startVerticalShift = mFromState.getVerticalProgress(mLauncher) * range;
+        float endVerticalShift = mToState.getVerticalProgress(mLauncher) * range;
+        float totalShift = endVerticalShift - startVerticalShift;
+        return 1 / totalShift;
     }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java
index 88a1e10..d9ce87c 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java
@@ -27,13 +27,13 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.views.OptionsPopupView;
 
 /**
  * Accessibility delegate with actions pointing to various Overview entry points.
  */
 public class OverviewAccessibilityDelegate extends AccessibilityDelegate {
 
-    private static final int OVERVIEW = R.string.accessibility_action_overview;
     private static final int WALLPAPERS = R.string.wallpaper_button_text;
     private static final int WIDGETS = R.string.widget_button_text;
     private static final int SETTINGS = R.string.settings_button_text;
@@ -43,7 +43,6 @@
         super.onInitializeAccessibilityNodeInfo(host, info);
 
         Context context = host.getContext();
-        info.addAction(new AccessibilityAction(OVERVIEW, context.getText(OVERVIEW)));
 
         if (Utilities.isWallpaperAllowed(context)) {
             info.addAction(new AccessibilityAction(WALLPAPERS, context.getText(WALLPAPERS)));
@@ -55,18 +54,13 @@
     @Override
     public boolean performAccessibilityAction(View host, int action, Bundle args) {
         Launcher launcher = Launcher.getLauncher(host.getContext());
-        OverviewPanel overviewPanel = launcher.findViewById(R.id.overview_panel);
-        if (action == OVERVIEW) {
-            launcher.getStateManager().goToState(LauncherState.OVERVIEW);
-            return true;
-        } else if (action == WALLPAPERS) {
+        if (action == WALLPAPERS) {
             launcher.onClickWallpaperPicker(host);
             return true;
         } else if (action == WIDGETS) {
-            overviewPanel.onClickAddWidgetButton();
-            return true;
+            return OptionsPopupView.onWidgetsClicked(launcher);
         } else if (action == SETTINGS) {
-            overviewPanel.onClickSettingsButton(host);
+            OptionsPopupView.startSettings(launcher);
             return true;
         }
         return super.performAccessibilityAction(host, action, args);
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
deleted file mode 100644
index 616e25c..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
+++ /dev/null
@@ -1,194 +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.launcher3.uioverrides;
-
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.WorkspaceStateTransitionAnimation.NO_ANIM_PROPERTY_SETTER;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.Toast;
-
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.WorkspaceStateTransitionAnimation.AnimatedPropertySetter;
-import com.android.launcher3.WorkspaceStateTransitionAnimation.PropertySetter;
-import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
-import com.android.launcher3.widget.WidgetsFullSheet;
-
-public class OverviewPanel extends LinearLayout implements Insettable, View.OnClickListener,
-        View.OnLongClickListener, LauncherStateManager.StateHandler {
-
-    // Out of 100, the percent of space the overview bar should try and take vertically.
-    private static final float OVERVIEW_ICON_ZONE_RATIO = 0.22f;
-
-    private final Launcher mLauncher;
-
-    public OverviewPanel(Context context) {
-        this(context, null);
-    }
-
-    public OverviewPanel(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public OverviewPanel(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        mLauncher = Launcher.getLauncher(context);
-        setAlpha(0);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        int visibleChildCount = 3;
-        // Attach buttons.
-        attachListeners(findViewById(R.id.wallpaper_button));
-        attachListeners(findViewById(R.id.widget_button));
-
-        View settingsButton = findViewById(R.id.settings_button);
-        if (mLauncher.hasSettings()) {
-            attachListeners(settingsButton);
-        } else {
-            settingsButton.setVisibility(GONE);
-            visibleChildCount--;
-        }
-
-        // Init UI
-        Resources res = getResources();
-        int itemWidthPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
-        int spacerWidthPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
-
-        int totalItemWidth = visibleChildCount * itemWidthPx;
-        int maxWidth = totalItemWidth + (visibleChildCount - 1) * spacerWidthPx;
-
-        getLayoutParams().width = Math.min(mLauncher.getDeviceProfile().availableWidthPx, maxWidth);
-        getLayoutParams().height = getButtonBarHeight(mLauncher);
-    }
-
-    private void attachListeners(View view) {
-        view.setOnClickListener(this);
-        view.setOnLongClickListener(this);
-    }
-
-    @Override
-    public void setInsets(Rect insets) {
-        ((FrameLayout.LayoutParams) getLayoutParams()).bottomMargin = insets.bottom;
-    }
-
-    @Override
-    public void onClick(View view) {
-        handleViewClick(view, Action.Touch.TAP);
-    }
-
-    @Override
-    public boolean onLongClick(View view) {
-        return handleViewClick(view, Action.Touch.LONGPRESS);
-    }
-
-    private boolean handleViewClick(View view, int action) {
-        if (mLauncher.getWorkspace().isSwitchingState()) {
-            return false;
-        }
-
-        final int controlType;
-        if (view.getId() == R.id.wallpaper_button) {
-            mLauncher.onClickWallpaperPicker(view);
-            controlType = ControlType.WALLPAPER_BUTTON;
-        } else if (view.getId() == R.id.widget_button) {
-            onClickAddWidgetButton();
-            controlType = ControlType.WIDGETS_BUTTON;
-        } else if (view.getId() == R.id.settings_button) {
-            onClickSettingsButton(view);
-            controlType = ControlType.SETTINGS_BUTTON;
-        } else {
-            return false;
-        }
-
-        mLauncher.getUserEventDispatcher().logActionOnControl(action, controlType);
-        return true;
-    }
-
-    /**
-     * Event handler for the (Add) Widgets button that appears after a long press
-     * on the home screen.
-     */
-    public void onClickAddWidgetButton() {
-        if (getContext().getPackageManager().isSafeMode()) {
-            Toast.makeText(mLauncher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
-        } else {
-            WidgetsFullSheet.show(mLauncher, true /* animated */);
-        }
-    }
-
-    /**
-     * Event handler for a click on the settings button that appears after a long press
-     * on the home screen.
-     */
-    public void onClickSettingsButton(View v) {
-        Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
-                .setPackage(getContext().getPackageName());
-        intent.setSourceBounds(mLauncher.getViewBounds(v));
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        getContext().startActivity(intent, mLauncher.getActivityLaunchOptionsAsBundle(v, false));
-    }
-
-    @Override
-    public void setState(LauncherState state) {
-        setState(state, NO_ANIM_PROPERTY_SETTER);
-    }
-
-    @Override
-    public void setStateWithAnimation(LauncherState toState,
-            AnimatorSetBuilder builder, AnimationConfig config) {
-        setState(toState, new AnimatedPropertySetter(config.duration, builder));
-    }
-
-    private void setState(LauncherState state, PropertySetter setter) {
-        float myAlpha = state == OVERVIEW ? 1 : 0;
-        setter.setViewAlpha(this, myAlpha, Interpolators.ACCEL);
-    }
-
-    public static int getButtonBarHeight(Launcher launcher) {
-        int zoneHeight = (int) (OVERVIEW_ICON_ZONE_RATIO *
-                launcher.getDeviceProfile().availableHeightPx);
-        Resources res = launcher.getResources();
-        int overviewModeMinIconZoneHeightPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
-        int overviewModeMaxIconZoneHeightPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
-        return Utilities.boundToRange(zoneHeight,
-                overviewModeMinIconZoneHeightPx,
-                overviewModeMaxIconZoneHeightPx);
-    }
-}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
index 37d0aa2..8def0d3 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
@@ -16,16 +16,8 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 
-import android.graphics.Rect;
-import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
@@ -33,56 +25,7 @@
  */
 public class OverviewState extends LauncherState {
 
-    // The percent to shrink the workspace during overview mode
-    private static final float SCALE_FACTOR = 0.7f;
-
-    private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE |
-            FLAG_DISABLE_PAGE_CLIPPING | FLAG_PAGE_BACKGROUNDS | FLAG_OVERVIEW_UI;
-
     public OverviewState(int id) {
-        super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
-    }
-
-    @Override
-    public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
-        DeviceProfile grid = launcher.getDeviceProfile();
-        Workspace ws = launcher.getWorkspace();
-        Rect insets = launcher.getDragLayer().getInsets();
-
-        int overviewButtonBarHeight = OverviewPanel.getButtonBarHeight(launcher);
-        int scaledHeight = (int) (SCALE_FACTOR * ws.getNormalChildHeight());
-        int workspaceTop = insets.top + grid.workspacePadding.top;
-        int workspaceBottom = ws.getHeight() - insets.bottom - grid.workspacePadding.bottom;
-        int overviewTop = insets.top;
-        int overviewBottom = ws.getHeight() - insets.bottom - overviewButtonBarHeight;
-        int workspaceOffsetTopEdge =
-                workspaceTop + ((workspaceBottom - workspaceTop) - scaledHeight) / 2;
-        int overviewOffsetTopEdge = overviewTop + (overviewBottom - overviewTop - scaledHeight) / 2;
-        return new float[] {SCALE_FACTOR, 0, -workspaceOffsetTopEdge + overviewOffsetTopEdge };
-    }
-
-    @Override
-    public float getHoseatAlpha(Launcher launcher) {
-        return 0;
-    }
-
-    @Override
-    public void onStateEnabled(Launcher launcher) {
-        launcher.getWorkspace().setPageRearrangeEnabled(true);
-
-        if (isAccessibilityEnabled(launcher)) {
-            launcher.getOverviewPanel().getChildAt(0).performAccessibilityAction(
-                    AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
-        }
-    }
-
-    @Override
-    public void onStateDisabled(Launcher launcher) {
-        launcher.getWorkspace().setPageRearrangeEnabled(false);
-    }
-
-    @Override
-    public View getFinalFocus(Launcher launcher) {
-        return launcher.getOverviewPanel();
+        super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, FLAG_DISABLE_RESTORE);
     }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java b/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java
deleted file mode 100644
index a7c8cee..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2016 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.uioverrides;
-
-import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.ScaleGestureDetector.OnScaleGestureListener;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.Workspace;
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.util.TouchController;
-
-/**
- * Detects pinches and animates the Workspace to/from overview mode.
- */
-public class PinchToOverviewListener extends AnimatorListenerAdapter
-        implements TouchController, OnScaleGestureListener {
-
-    private static final float ACCEPT_THRESHOLD = 0.65f;
-    /**
-     * The velocity threshold at which a pinch will be completed instead of canceled,
-     * even if the first threshold has not been passed. Measured in scale / millisecond
-     */
-    private static final float FLING_VELOCITY = 0.001f;
-
-    private final ScaleGestureDetector mPinchDetector;
-    private Launcher mLauncher;
-    private Workspace mWorkspace = null;
-    private boolean mPinchStarted = false;
-
-    private AnimatorPlaybackController mCurrentAnimation;
-    private float mCurrentScale;
-    private boolean mShouldGoToFinalState;
-
-    private LauncherState mToState;
-
-    public PinchToOverviewListener(Launcher launcher) {
-        mLauncher = launcher;
-        mPinchDetector = new ScaleGestureDetector(mLauncher, this);
-    }
-
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        mPinchDetector.onTouchEvent(ev);
-        return mPinchStarted;
-    }
-
-    public boolean onControllerTouchEvent(MotionEvent ev) {
-        return mPinchDetector.onTouchEvent(ev);
-    }
-
-    @Override
-    public boolean onScaleBegin(ScaleGestureDetector detector) {
-        if (isAccessibilityEnabled(mLauncher)) {
-            return false;
-        }
-        if (!mLauncher.isInState(NORMAL) && !mLauncher.isInState(OVERVIEW)) {
-            // Don't listen for the pinch gesture if on all apps, widget picker, -1, etc.
-            return false;
-        }
-        if (mCurrentAnimation != null) {
-            // Don't listen for the pinch gesture if we are already animating from a previous one.
-            return false;
-        }
-        if (mLauncher.isWorkspaceLocked()) {
-            // Don't listen for the pinch gesture if the workspace isn't ready.
-            return false;
-        }
-        if (mWorkspace == null) {
-            mWorkspace = mLauncher.getWorkspace();
-        }
-        if (mWorkspace.isSwitchingState()) {
-            // Don't listen for the pinch gesture while switching state, as it will cause a jump
-            // once the state switching animation is complete.
-            return false;
-        }
-        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
-            // Don't listen for the pinch gesture if a floating view is open.
-            return false;
-        }
-
-        if (mLauncher.getDragController().isDragging()) {
-            mLauncher.getDragController().cancelDrag();
-        }
-
-        mToState = mLauncher.isInState(OVERVIEW) ? NORMAL : OVERVIEW;
-        mCurrentAnimation = mLauncher.getStateManager()
-                .createAnimationToNewWorkspace(mToState, OVERVIEW_TRANSITION_MS);
-        mCurrentAnimation.getTarget().addListener(this);
-        mPinchStarted = true;
-        mCurrentScale = 1;
-        mShouldGoToFinalState = false;
-
-        mCurrentAnimation.dispatchOnStart();
-        return true;
-    }
-
-    @Override
-    public void onAnimationEnd(Animator animation) {
-        mCurrentAnimation = null;
-        mPinchStarted = false;
-    }
-
-    @Override
-    public void onScaleEnd(ScaleGestureDetector detector) {
-        if (mShouldGoToFinalState) {
-            mCurrentAnimation.start();
-        } else {
-            mCurrentAnimation.setEndAction(new Runnable() {
-                @Override
-                public void run() {
-                    mLauncher.getStateManager().goToState(
-                            mToState == OVERVIEW ? NORMAL : OVERVIEW, false);
-                }
-            });
-            mCurrentAnimation.reverse();
-        }
-    }
-
-    @Override
-    public boolean onScale(ScaleGestureDetector detector) {
-        mCurrentScale = detector.getScaleFactor() * mCurrentScale;
-
-        // If we are zooming out, inverse the mCurrentScale so that animationFraction = [0, 1]
-        // 0 => Animation complete
-        // 1=> Animation started
-        float animationFraction = mToState == OVERVIEW ? mCurrentScale : (1 / mCurrentScale);
-
-        float velocity = (1 - detector.getScaleFactor()) / detector.getTimeDelta();
-        if (Math.abs(velocity) >= FLING_VELOCITY) {
-            LauncherState toState = velocity > 0 ? OVERVIEW : NORMAL;
-            mShouldGoToFinalState = toState == mToState;
-        } else {
-            mShouldGoToFinalState = animationFraction <= ACCEPT_THRESHOLD;
-        }
-
-        // Move the transition animation to that duration.
-        mCurrentAnimation.setPlayFraction(1 - animationFraction);
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index de75ac9..1227dfe 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -16,9 +16,6 @@
 
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-import android.graphics.PointF;
 import android.view.View.AccessibilityDelegate;
 
 import com.android.launcher3.Launcher;
@@ -29,7 +26,7 @@
 
     public static TouchController[] createTouchControllers(Launcher launcher) {
         return new TouchController[] {
-                new AllAppsSwipeController(launcher), new PinchToOverviewListener(launcher)};
+                launcher.getDragController(), new AllAppsSwipeController(launcher)};
     }
 
     public static AccessibilityDelegate newPageIndicatorAccessibilityDelegate() {
@@ -38,14 +35,9 @@
 
     public static StateHandler[] getStateHandler(Launcher launcher) {
         return new StateHandler[] {
-                (OverviewPanel) launcher.getOverviewPanel(),
                 launcher.getAllAppsController(), launcher.getWorkspace() };
     }
 
-    public static void onWorkspaceLongPress(Launcher launcher, PointF touchPoint) {
-        launcher.getStateManager().goToState(OVERVIEW);
-    }
-
     public static void resetOverview(Launcher launcher) { }
 
     public static void onLauncherStateOrFocusChanged(Launcher launcher) { }