Merge "Fix logic to get icon from any view that is a child of DeepShortcutView." into ub-launcher3-master
diff --git a/proguard.flags b/proguard.flags
index cac8930..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);
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 9006831..e414fa0 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 54190ec..e2f9ba8 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"
@@ -22,6 +22,7 @@
     android:clipChildren="false"
     android:clipToPadding="false"
     android:alpha="0.0"
-    android:visibility="invisible" >
+    android:visibility="invisible"
+    android:focusableInTouchMode="true" >
 
-</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 6a76b45..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>
@@ -36,7 +34,7 @@
     <!-- 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/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 6cd7b73..5a090d9 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;
 
@@ -41,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;
@@ -59,9 +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;
@@ -80,7 +80,6 @@
         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 =
@@ -99,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;
@@ -107,17 +108,23 @@
     private final float mRecentsTransY;
     private final float mRecentsScale;
 
+    private DeviceProfile mDeviceProfile;
     private View mFloatingView;
-    private boolean mIsRtl;
 
-    private LauncherTransitionAnimator mCurrentAnimator;
+    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);
@@ -135,26 +142,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.
@@ -162,58 +149,26 @@
     @Override
     public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
         if (hasControlRemoteAppTransitionPermission()) {
-            TaskView taskView = findTaskViewToLaunch(launcher, v);
             try {
-                RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mLauncher) {
+                RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler) {
+
                     @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(
-                                    taskView == null ? v : taskView, 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;
-                            }
-
-                            setCurrentAnimator(animator);
-                            mAnimator = animator.getAnimatorSet();
-                            mAnimator.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();
-                                }
-                            });
-                            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);
-                        });
+                    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));
+                        }
+                        mLauncher.getStateManager().setCurrentAnimation(anim);
+                        return anim;
                     }
                 };
 
-                int duration = taskView != null ? RECENTS_LAUNCH_DURATION : APP_LAUNCH_DURATION;
+                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));
@@ -232,18 +187,19 @@
      * 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 TaskView findTaskViewToLaunch(Launcher launcher, View v) {
+    private TaskView findTaskViewToLaunch(
+            BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets) {
         if (v instanceof TaskView) {
             return (TaskView) v;
         }
-        if (!launcher.isInState(LauncherState.OVERVIEW)) {
-            return null;
-        }
+        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) {
-                RecentsView recentsView = launcher.getOverviewPanel();
                 for (int i = 0; i < recentsView.getChildCount(); i++) {
                     TaskView taskView = (TaskView) recentsView.getPageAt(i);
                     if (recentsView.isTaskViewVisible(taskView)) {
@@ -255,32 +211,10 @@
                 }
             }
         }
-        return null;
-    }
 
-    /**
-     * Composes the animations for a launch from the recents list if possible.
-     */
-    private LauncherTransitionAnimator composeRecentsLaunchAnimator(View v,
-            RemoteAnimationTargetCompat[] targets) {
-        RecentsView recentsView = mLauncher.getOverviewPanel();
-        boolean launcherClosing = launcherIsATargetWithMode(targets, MODE_CLOSING);
-        MutableBoolean skipLauncherChanges = new MutableBoolean(!launcherClosing);
-        if (v instanceof TaskView) {
-            // We already found a task view to launch, so use that for the animation.
-            TaskView taskView = (TaskView) v;
-            return new LauncherTransitionAnimator(getRecentsLauncherAnimator(recentsView, taskView),
-                    getRecentsWindowAnimator(taskView, skipLauncherChanges, targets));
-        }
-
-        // 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.
-
-        // Ensure recents is actually visible
-        if (!mLauncher.getStateManager().getState().overviewUi) {
+        if (targets == null) {
             return null;
         }
-
         // Resolve the opening task id
         int openingTaskId = -1;
         for (RemoteAnimationTargetCompat target : targets) {
@@ -301,19 +235,35 @@
         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;
+        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()
@@ -330,7 +280,7 @@
 
         Animator windowAnim = getRecentsWindowAnimator(taskView, skipLauncherChanges, targets);
         windowAnim.addListener(windowAnimEndListener);
-        return new LauncherTransitionAnimator(launcherAnim, windowAnim, skipLauncherChanges);
+        return new Animator[] {launcherAnim, windowAnim};
     }
 
     /**
@@ -372,26 +322,24 @@
         } 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 >= 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);
             }
         }
 
@@ -420,7 +368,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);
@@ -456,7 +404,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);
@@ -471,9 +419,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();
@@ -487,7 +434,7 @@
                         t.setMatrix(target.leash, matrix);
                         t.setWindowCrop(target.leash, crop);
 
-                        if (!skipLauncherChanges.value) {
+                        if (!skipLauncherChanges) {
                             t.deferTransactionUntil(target.leash, surface, frameNumber);
                         }
                     }
@@ -505,15 +452,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) {
@@ -543,7 +481,9 @@
 
         if (mLauncher.isInState(LauncherState.ALL_APPS) && !mDeviceProfile.isVerticalBarLayout()) {
             // All Apps in portrait mode is full screen, so we only animate AllAppsContainerView.
-            View appsView = mLauncher.getAppsView();
+            final View appsView = mLauncher.getAppsView();
+            final float startAlpha = appsView.getAlpha();
+            final float startY = appsView.getTranslationY();
             appsView.setAlpha(alphas[0]);
             appsView.setTranslationY(trans[0]);
 
@@ -556,6 +496,14 @@
 
             launcherAnimator.play(alpha);
             launcherAnimator.play(transY);
+
+            launcherAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    appsView.setAlpha(startAlpha);
+                    appsView.setTranslationY(startY);
+                }
+            });
         } else {
             mDragLayer.setAlpha(alphas[0]);
             mDragLayer.setTranslationY(trans[0]);
@@ -570,6 +518,13 @@
 
             launcherAnimator.play(dragLayerAlpha);
             launcherAnimator.play(dragLayerTransY);
+            launcherAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mDragLayer.setAlpha(1);
+                    mDragLayer.setTranslationY(0);
+                }
+            });
         }
         return launcherAnimator;
     }
@@ -596,10 +551,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 && !fromDeepShortcutView) {
@@ -612,12 +565,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);
@@ -662,6 +627,14 @@
         alpha.setInterpolator(Interpolators.LINEAR);
         appIconAnimatorSet.play(alpha);
 
+        appIconAnimatorSet.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                // Reset launcher to normal state
+                v.setVisibility(View.VISIBLE);
+                ((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
+            }
+        });
         return appIconAnimatorSet;
     }
 
@@ -725,9 +698,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
@@ -772,6 +744,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 */));
 
@@ -799,40 +772,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;
             }
         };
     }
@@ -903,12 +861,17 @@
             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);
@@ -936,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..2626e7c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -24,7 +24,7 @@
 import com.android.launcher3.AbstractFloatingView;
 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;
 
 /**
@@ -32,8 +32,7 @@
  */
 public class AllAppsState extends LauncherState {
 
-    private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY
-            | FLAG_SHOW_SCRIM | FLAG_ALL_APPS_SCRIM;
+    private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY | FLAG_ALL_APPS_SCRIM;
 
     private static final PageAlphaProvider PAGE_ALPHA_PROVIDER = new PageAlphaProvider(DEACCEL_2) {
         @Override
@@ -58,7 +57,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,8 +83,15 @@
     }
 
     @Override
-    public float getHoseatAlpha(Launcher launcher) {
-        return 0;
+    public int getVisibleElements(Launcher launcher) {
+        return ALL_APPS_HEADER | ALL_APPS_HEADER_EXTRA | ALL_APPS_CONTENT;
+    }
+
+    @Override
+    public float[] getOverviewTranslationFactor(Launcher launcher) {
+        // Keep the same translation as in overview, so that we don't slide around when
+        // transitioning to All Apps.
+        return LauncherState.OVERVIEW.getOverviewTranslationFactor(launcher);
     }
 
     @Override
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 8a5c55a..99bf264 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
@@ -15,9 +15,10 @@
  */
 package com.android.launcher3.uioverrides;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.quickstep.QuickScrubController;
-import com.android.quickstep.RecentsView;
+import com.android.quickstep.views.RecentsView;
 
 /**
  * Extension of overview state used for QuickScrub
@@ -25,12 +26,10 @@
 public class FastOverviewState extends OverviewState {
 
     private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_DISABLE_RESTORE
-            | FLAG_DISABLE_INTERACTION | FLAG_OVERVIEW_UI;
-
-    private static final boolean DEBUG_DIFFERENT_UI = false;
+            | FLAG_DISABLE_INTERACTION | FLAG_OVERVIEW_UI | FLAG_HIDE_BACK_BUTTON;
 
     public FastOverviewState(int id) {
-        super(id, QuickScrubController.QUICK_SWITCH_START_DURATION, STATE_FLAGS);
+        super(id, QuickScrubController.QUICK_SCRUB_START_DURATION, STATE_FLAGS);
     }
 
     @Override
@@ -40,11 +39,18 @@
         recentsView.getQuickScrubController().onFinishedTransitionToQuickScrub();
     }
 
+    public void onStateEnabled(Launcher launcher) {
+        super.onStateEnabled(launcher);
+        AbstractFloatingView.closeAllOpenViews(launcher);
+    }
+
     @Override
-    public float getHoseatAlpha(Launcher launcher) {
-        if (DEBUG_DIFFERENT_UI) {
-            return 0;
-        }
-        return super.getHoseatAlpha(launcher);
+    public int getVisibleElements(Launcher launcher) {
+        return NONE;
+    }
+
+    @Override
+    public float[] getOverviewTranslationFactor(Launcher launcher) {
+        return new float[] {0f, 0.5f};
     }
 }
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 a88369d..abbf45e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -22,11 +22,12 @@
 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.Workspace;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.quickstep.RecentsView;
+import com.android.quickstep.views.RecentsView;
 
 /**
  * Definition for overview state
@@ -57,6 +58,11 @@
     }
 
     @Override
+    public float[] getOverviewTranslationFactor(Launcher launcher) {
+        return new float[] {0f, 0f};
+    }
+
+    @Override
     public void onStateEnabled(Launcher launcher) {
         RecentsView rv = launcher.getOverviewPanel();
         rv.setOverviewStateEnabled(true);
@@ -100,4 +106,30 @@
 
         return new float[] {scale, 0, translationY};
     }
+
+    @Override
+    public int getVisibleElements(Launcher launcher) {
+        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
+            // TODO: Remove hotseat from overview
+            return HOTSEAT_ICONS;
+        } else {
+            return launcher.getAppsView().getFloatingHeaderView().hasVisibleContent()
+                    ? HOTSEAT_EXTRA | ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS | HOTSEAT_EXTRA;
+        }
+    }
+
+    @Override
+    public float getVerticalProgress(Launcher launcher) {
+        if ((getVisibleElements(launcher) & ALL_APPS_HEADER_EXTRA) == 0) {
+            // We have no all apps content, so we're still at the fully down progress.
+            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..1e006e5
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -0,0 +1,291 @@
+/*
+ * 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 static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.view.MotionEvent;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.touch.AbstractStateChangeTouchController;
+import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+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 {
+
+    private static final float TOTAL_DISTANCE_MULTIPLIER = 2f;
+    private static final float LINEAR_SCALE_LIMIT = 1 / TOTAL_DISTANCE_MULTIPLIER;
+
+    // Much be greater than LINEAR_SCALE_LIMIT;
+    private static final float MAXIMUM_DISTANCE_FACTOR = 0.9f;
+
+    // Maximum amount to overshoot.
+    private static final float MAX_OVERSHOOT = 0.3f;
+
+    private static final double PI_BY_2 = Math.PI / 2;
+
+    private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
+
+    // If > 0, the animation progress is clamped at that value as long as user is dragging.
+    private float mClampProgressUpdate = -1;
+
+    // If true, we will finish the current animation instantly on second touch.
+    private boolean mFinishFastOnSecondTouch;
+
+    private final Interpolator mAllAppsDampedInterpolator = new Interpolator() {
+
+        private final double mAngleMultiplier = Math.PI /
+                (2 * (MAXIMUM_DISTANCE_FACTOR - LINEAR_SCALE_LIMIT));
+
+        @Override
+        public float getInterpolation(float v) {
+            if (v <= LINEAR_SCALE_LIMIT) {
+                return v * TOTAL_DISTANCE_MULTIPLIER;
+            }
+            float overshoot = (v - LINEAR_SCALE_LIMIT);
+            return (float) (1 + MAX_OVERSHOOT * Math.sin(overshoot * mAngleMultiplier));
+        }
+    };
+
+    private final Interpolator mOverviewBoundInterpolator = (v) -> {
+            if (v >= MAXIMUM_DISTANCE_FACTOR) {
+                return 1;
+            }
+            return FAST_OUT_SLOW_IN.getInterpolation(v / MAXIMUM_DISTANCE_FACTOR);
+    };
+
+    public PortraitStatesTouchController(Launcher l) {
+        super(l, SwipeDetector.VERTICAL);
+    }
+
+    @Override
+    protected boolean canInterceptTouch(MotionEvent ev) {
+        if (mCurrentAnimation != null) {
+            if (mFinishFastOnSecondTouch) {
+                // TODO: Animate to finish instead.
+                mCurrentAnimation.getAnimationPlayer().end();
+            }
+
+            // 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;
+        }
+    }
+
+    private AnimatorSetBuilder getNormalToOverviewAnimation() {
+        mAllAppsInterpolatorWrapper.baseInterpolator = mAllAppsDampedInterpolator;
+
+        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+        builder.setInterpolator(ANIM_VERTICAL_PROGRESS, mAllAppsInterpolatorWrapper);
+
+        builder.setInterpolator(ANIM_OVERVIEW_TRANSLATION, mOverviewBoundInterpolator);
+        return builder;
+    }
+
+    @Override
+    protected void updateProgress(float fraction) {
+        if (mClampProgressUpdate > 0) {
+            mCurrentAnimation.setPlayFraction(Math.min(fraction, mClampProgressUpdate));
+        } else {
+            super.updateProgress(fraction);
+        }
+    }
+
+    @Override
+    protected float initCurrentAnimation() {
+        float range = getShiftRange();
+        long maxAccuracy = (long) (2 * range);
+
+        float startVerticalShift = mFromState.getVerticalProgress(mLauncher) * range;
+        float endVerticalShift = mToState.getVerticalProgress(mLauncher) * range;
+
+        float totalShift = endVerticalShift - startVerticalShift;
+
+        final AnimatorSetBuilder builder;
+
+        if (mFromState == NORMAL && mToState == OVERVIEW && totalShift != 0) {
+            builder = getNormalToOverviewAnimation();
+            totalShift = totalShift * TOTAL_DISTANCE_MULTIPLIER;
+            mClampProgressUpdate = MAXIMUM_DISTANCE_FACTOR;
+        } else {
+            builder = new AnimatorSetBuilder();
+            mClampProgressUpdate = -1;
+        }
+
+        mCurrentAnimation = mLauncher.getStateManager()
+                .createAnimationToNewWorkspace(mToState, builder, maxAccuracy);
+
+        if (totalShift == 0) {
+            totalShift = Math.signum(mFromState.ordinal - mToState.ordinal)
+                    * OverviewState.getDefaultSwipeHeight(mLauncher);
+        }
+        return 1 / totalShift;
+    }
+
+    @Override
+    protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
+            LauncherState targetState, float velocity, boolean isFling) {
+        if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
+            mFinishFastOnSecondTouch = true;
+
+            // Update all apps interpolator
+            float currentFraction = mCurrentAnimation.getProgressFraction();
+            float absVelocity = Math.abs(velocity);
+            float currentValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction);
+
+            if (isFling && absVelocity > 1 && currentFraction < LINEAR_SCALE_LIMIT) {
+
+                // TODO: Clean up these magic calculations
+                // Linearly interpolate the max value based on the velocity.
+                float maxValue = Math.max(absVelocity > 4 ? 1 + MAX_OVERSHOOT :
+                        1 + (absVelocity - 1) * MAX_OVERSHOOT / 3,
+                        currentValue);
+                double angleToPeak = PI_BY_2 - Math.asin(currentValue / maxValue);
+
+                if (expectedDuration != 0 && angleToPeak != 0) {
+
+                    float distanceLeft = 1 - currentFraction;
+                    mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> {
+                        float scaledF = (f - currentFraction) / distanceLeft;
+
+                        if (scaledF < 0.5f) {
+                            double angle = PI_BY_2 - angleToPeak + scaledF * angleToPeak / 0.5f;
+                            return (float) (maxValue * Math.sin(angle));
+                        }
+
+                        scaledF = ((scaledF - .5f) / .5f);
+                        double angle = PI_BY_2 + 3 * scaledF * PI_BY_2;
+                        float amplitude = (1 - scaledF) * (1 - scaledF) * (maxValue - 1);
+                        return 1 + (float) (amplitude * Math.sin(angle));
+                    };
+
+                    animator.setDuration(expectedDuration).setInterpolator(LINEAR);
+                    return;
+                }
+            }
+
+            if (currentFraction < LINEAR_SCALE_LIMIT) {
+                mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;
+                super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
+                        velocity, isFling);
+                return;
+            }
+            float extraValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction) - 1;
+            float distanceLeft = 1 - currentFraction;
+
+            animator.setFloatValues(currentFraction, 1);
+            mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> {
+                float scaledF = (f - currentFraction) / distanceLeft;
+
+                double angle = scaledF * 1.5 * Math.PI;
+                float amplitude = (1 - scaledF) * (1 - scaledF) * extraValue;
+                return 1 + (float) (amplitude * Math.sin(angle));
+            };
+            animator.setDuration(200).setInterpolator(LINEAR);
+            return;
+        }
+        mFinishFastOnSecondTouch = false;
+        super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
+                velocity, isFling);
+    }
+
+    @Override
+    protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
+        super.onSwipeInteractionCompleted(targetState, logAction);
+        if (mFromState == NORMAL && targetState == OVERVIEW) {
+            SysuiEventLogger.writeDummyRecentsTransition(0);
+        }
+    }
+
+    private static class InterpolatorWrapper implements Interpolator {
+
+        public TimeInterpolator baseInterpolator = LINEAR;
+
+        @Override
+        public float getInterpolation(float v) {
+            return baseInterpolator.getInterpolation(v);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 2a2e9c5..b993c3c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -16,63 +16,55 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.AlphaUpdateListener.updateVisibility;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
+import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_X_FACTOR;
+import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.view.View;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.os.Build;
 
 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.PagedView;
-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.launcher3.anim.PropertySetter;
+import com.android.quickstep.views.LauncherRecentsView;
 
+@TargetApi(Build.VERSION_CODES.O)
 public class RecentsViewStateController implements StateHandler {
 
     private final Launcher mLauncher;
-    private final RecentsView mRecentsView;
-
-    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 mIsRecentsSlidingInOrOut;
+    private final LauncherRecentsView mRecentsView;
 
     public RecentsViewStateController(Launcher launcher) {
         mLauncher = launcher;
         mRecentsView = launcher.getOverviewPanel();
-        mRecentsView.setStateController(this);
     }
 
     @Override
     public void setState(LauncherState state) {
-        setVisibility(state.overviewUi);
-        setTransitionProgress(state.overviewUi ? 1 : 0);
+        mRecentsView.setAlpha(state.overviewUi ? 1 : 0);
+        updateVisibility(mRecentsView, isAccessibilityEnabled(mLauncher));
+        float[] translationFactor = state.getOverviewTranslationFactor(mLauncher);
+        mRecentsView.setTranslationXFactor(translationFactor[0]);
+        mRecentsView.setTranslationYFactor(translationFactor[1]);
         if (state.overviewUi) {
-            for (int i = 0; i < mRecentsView.getPageCount(); i++) {
-                ((TaskView) mRecentsView.getPageAt(i)).resetVisualProperties();
-            }
-            mRecentsView.updateCurveProperties();
+            mRecentsView.resetTaskVisuals();
         }
     }
 
     @Override
     public void setStateWithAnimation(final LauncherState toState,
             AnimatorSetBuilder builder, AnimationConfig config) {
-        LauncherState fromState = mLauncher.getStateManager().getState();
-        mIsRecentsSlidingInOrOut = fromState == NORMAL && toState.overviewUi
-                || fromState.overviewUi && toState == NORMAL;
 
         // Scroll to the workspace card before changing to the NORMAL state.
+        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;
@@ -83,74 +75,24 @@
             builder.setStartDelay(snapDuration / 4);
         }
 
-        ObjectAnimator progressAnim =
-                mTransitionProgress.animateToValue(toState.overviewUi ? 1 : 0);
-        progressAnim.setDuration(config.duration);
-        progressAnim.setInterpolator(Interpolators.LINEAR);
-        progressAnim.addListener(new AnimationSuccessListener() {
+        PropertySetter setter = config.getProperSetter(builder);
+        float[] translationFactor = toState.getOverviewTranslationFactor(mLauncher);
+        setter.setFloat(mRecentsView, TRANSLATION_X_FACTOR,
+                translationFactor[0],
+                builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
+        setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR,
+                translationFactor[1],
+                builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
+        setter.setViewAlpha(mRecentsView, toState.overviewUi ? 1 : 0, LINEAR);
 
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
-            }
-        });
-        builder.play(progressAnim);
-
-        ObjectAnimator visibilityAnim = animateVisibility(toState.overviewUi);
-        visibilityAnim.setDuration(config.duration);
-        visibilityAnim.setInterpolator(Interpolators.LINEAR);
-        builder.play(visibilityAnim);
-    }
-
-    public void setVisibility(boolean isVisible) {
-        mVisibilityMultiplier.cancelAnimation();
-        mRecentsView.setVisibility(isVisible ? View.VISIBLE : View.GONE);
-        mVisibilityMultiplier.updateValue(isVisible ? 1 : 0);
-    }
-
-    public ObjectAnimator animateVisibility(boolean isVisible) {
-        ObjectAnimator anim = mVisibilityMultiplier.animateToValue(isVisible ? 1 : 0);
-        if (isVisible) {
-            anim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    mRecentsView.setVisibility(View.VISIBLE);
-                }
+        if (toState.overviewUi) {
+            ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
+            updateAnim.addUpdateListener(valueAnimator -> {
+                // While animating into recents, update the visible task data as needed
+                mRecentsView.loadVisibleTaskData();
             });
-        } else {
-            anim.addListener(new AnimationSuccessListener() {
-                @Override
-                public void onAnimationSuccess(Animator animator) {
-                    mRecentsView.setVisibility(View.GONE);
-                }
-            });
+            updateAnim.setDuration(config.duration);
+            builder.play(updateAnim);
         }
-        return anim;
-    }
-
-    public void setTransitionProgress(float progress) {
-        mTransitionProgress.cancelAnimation();
-        mTransitionProgress.updateValue(progress);
-    }
-
-    private void onTransitionProgress() {
-        applyProgress();
-        if (mIsRecentsSlidingInOrOut) {
-            float interpolatedProgress = ACCEL.getInterpolation(mTransitionProgress.value);
-            // Slide in from the side as we swipe.
-            int translation = mRecentsView.getWidth();
-            if (mRecentsView.isRtl()) {
-                translation = -translation;
-            }
-            mRecentsView.setTranslationX(translation * (1 - interpolatedProgress));
-        }
-    }
-
-    private void onVisibilityProgress() {
-        applyProgress();
-    }
-
-    private void applyProgress() {
-        mRecentsView.setAlpha(mTransitionProgress.value * mVisibilityMultiplier.value);
     }
 }
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 66%
rename from quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
rename to quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index eb14d60..d11547d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -15,17 +15,12 @@
  */
 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.DEACCEL_1_5;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
 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;
@@ -33,23 +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.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TouchController;
-import com.android.quickstep.RecentsView;
-import com.android.quickstep.TaskView;
+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,22 +113,14 @@
                 ignoreSlopWhenSettling = true;
             } else {
                 mTaskBeingDragged = null;
-                mSwipeDownEnabled = true;
 
                 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;
-                    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;
                 }
             }
@@ -168,9 +143,6 @@
     }
 
     private void reInitAnimationController(boolean goingUp) {
-        if (!goingUp && !mSwipeDownEnabled) {
-            goingUp = true;
-        }
         if (mCurrentAnimation != null && mCurrentAnimationIsGoingUp == goingUp) {
             // No need to init
             return;
@@ -178,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);
@@ -253,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()
@@ -281,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();
@@ -292,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..846e803 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -16,37 +16,46 @@
 
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.Utilities.getPrefs;
+import static com.android.quickstep.OverviewInteractionState.KEY_SWIPE_UP_ENABLED;
 
-import android.graphics.PointF;
+import android.content.Context;
+import android.content.SharedPreferences;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Hotseat;
 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.R;
 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) {
+        SharedPreferences prefs = getPrefs(launcher);
+        boolean swipeUpEnabled = prefs.getBoolean(KEY_SWIPE_UP_ENABLED, true);
+        if (!swipeUpEnabled) {
             return new TouchController[] {
-                    new EdgeSwipeController(launcher),
-                    new TwoStepSwipeController(launcher),
-                    new OverviewSwipeController(launcher)};
+                    launcher.getDragController(),
+                    new LandscapeStatesTouchController(launcher),
+                    new TaskViewTouchController(launcher)};
+        }
+        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
+            return new TouchController[] {
+                    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,26 +69,16 @@
                 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)
-                || !launcher.hasWindowFocus();
-        if (!shouldBackButtonBeVisible) {
+        boolean shouldBackButtonBeHidden = launcher != null
+                && launcher.getStateManager().getState().hideBackButton
+                && launcher.hasWindowFocus();
+        if (shouldBackButtonBeHidden) {
             // Show the back button if there is a floating view visible.
-            DragLayer dragLayer = launcher.getDragLayer();
-            for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
-                View child = dragLayer.getChildAt(i);
-                if (child instanceof AbstractFloatingView) {
-                    shouldBackButtonBeVisible = true;
-                    break;
-                }
-            }
+            shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenView(launcher) == null;
         }
-        OverviewInteractionState.setBackButtonVisible(launcher, shouldBackButtonBeVisible);
+        OverviewInteractionState.getInstance(launcher)
+                .setBackButtonVisible(!shouldBackButtonBeHidden);
     }
 
     public static void resetOverview(Launcher launcher) {
@@ -87,17 +86,24 @@
         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);
         }
     }
+
+    public static View[] getHotseatExtraContent(Hotseat hotseat) {
+        return new View[] {
+                hotseat.findViewById(R.id.drag_indicator),
+                hotseat.findViewById(R.id.search_container_hotseat),
+        };
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
new file mode 100644
index 0000000..9e2e5ac
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -0,0 +1,367 @@
+/*
+ * 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.support.annotation.Nullable;
+import android.support.annotation.UiThread;
+import android.view.View;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherInitListener;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.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);
+
+    @UiThread
+    @Nullable
+    RecentsView getVisibleRecentsView();
+
+    @UiThread
+    boolean switchToRecentsIfVisible();
+
+    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);
+        }
+
+        @Nullable
+        @UiThread
+        private Launcher getVisibleLaucher() {
+            LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+            if (app == null) {
+                return null;
+            }
+            Launcher launcher = (Launcher) app.getModel().getCallback();
+            return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ?
+                    launcher : null;
+        }
+
+        @Nullable
+        @Override
+        public RecentsView getVisibleRecentsView() {
+            Launcher launcher = getVisibleLaucher();
+            return launcher != null && launcher.isInState(OVERVIEW)
+                    ? launcher.getOverviewPanel() : null;
+        }
+
+        @Override
+        public boolean switchToRecentsIfVisible() {
+            Launcher launcher = getVisibleLaucher();
+            if (launcher != null) {
+                launcher.getStateManager().goToState(OVERVIEW);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    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());
+        }
+
+        @Nullable
+        @Override
+        public RecentsView getVisibleRecentsView() {
+            RecentsActivity activity = RecentsActivityTracker.getCurrentActivity();
+            if (activity != null && activity.hasWindowFocus()) {
+                return activity.getOverviewPanel();
+            }
+            return null;
+        }
+
+        @Override
+        public boolean switchToRecentsIfVisible() {
+            return false;
+        }
+    }
+
+    interface LayoutListener {
+
+        void open();
+
+        void setHandler(WindowTransformSwipeHandler handler);
+
+        void finish();
+    }
+
+    interface ActivityInitListener {
+
+        void register();
+
+        void unregister();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
deleted file mode 100644
index 5871a6d..0000000
--- a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.support.annotation.WorkerThread;
-
-import com.android.launcher3.states.InternalStateHandler;
-import com.android.quickstep.TouchConsumer.InteractionType;
-
-public abstract class BaseSwipeInteractionHandler extends InternalStateHandler {
-
-    protected Runnable mGestureEndCallback;
-    protected boolean mIsGoingToHome;
-
-    public void setGestureEndCallback(Runnable gestureEndCallback) {
-        mGestureEndCallback = gestureEndCallback;
-    }
-
-    public void reset() {}
-
-    @WorkerThread
-    public abstract void onGestureStarted();
-
-    @WorkerThread
-    public abstract void onGestureEnded(float endVelocity);
-
-    public abstract void updateInteractionType(@InteractionType int interactionType);
-
-    @WorkerThread
-    public abstract void onQuickScrubEnd();
-
-    @WorkerThread
-    public abstract void onQuickScrubProgress(float progress);
-
-    @WorkerThread
-    public abstract void updateDisplacement(float displacement);
-}
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityOptions.java b/quickstep/src/com/android/quickstep/FallbackActivityOptions.java
new file mode 100644
index 0000000..3a7fb2d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/FallbackActivityOptions.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import android.graphics.Rect;
+import android.util.Log;
+
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import com.android.systemui.shared.system.RecentsAnimationListener;
+import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+/**
+ * Temporary class to create activity options to emulate recents transition for fallback activtiy.
+ */
+public class FallbackActivityOptions implements RemoteAnimationRunnerCompat {
+
+    private final RecentsAnimationListener mListener;
+
+    public FallbackActivityOptions(RecentsAnimationListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats,
+            Runnable runnable) {
+        DummyRecentsAnimationControllerCompat dummyRecentsAnim =
+                new DummyRecentsAnimationControllerCompat(runnable);
+
+        Rect insets = new Rect();
+        WindowManagerWrapper.getInstance().getStableInsets(insets);
+        mListener.onAnimationStart(dummyRecentsAnim, targetCompats, insets, null);
+    }
+
+    @Override
+    public void onAnimationCancelled() {
+        mListener.onAnimationCanceled();
+    }
+
+    private static class DummyRecentsAnimationControllerCompat
+            extends RecentsAnimationControllerCompat {
+
+        final Runnable mFinishCallback;
+
+        public DummyRecentsAnimationControllerCompat(Runnable finishCallback) {
+            mFinishCallback = finishCallback;
+        }
+
+        @Override
+        public ThumbnailData screenshotTask(int taskId) {
+            return new ThumbnailData();
+        }
+
+        @Override
+        public void setInputConsumerEnabled(boolean enabled) { }
+
+        @Override
+        public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) { }
+
+        @Override
+        public void finish(boolean toHome) {
+            if (toHome) {
+                mFinishCallback.run();
+            }
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/FallbackRecentsView.java
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/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 94b6faa..8e6e4c7 100644
--- a/quickstep/src/com/android/quickstep/MotionEventQueue.java
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -19,9 +19,7 @@
 import static android.view.MotionEvent.ACTION_MASK;
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_POINTER_INDEX_SHIFT;
-
 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
-import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SWITCH;
 
 import android.annotation.TargetApi;
 import android.os.Build;
@@ -43,17 +41,17 @@
 
     private static final int ACTION_VIRTUAL = ACTION_MASK - 1;
 
-    private static final int ACTION_QUICK_SWITCH =
-            ACTION_VIRTUAL | (1 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_QUICK_SCRUB_START =
-            ACTION_VIRTUAL | (2 << ACTION_POINTER_INDEX_SHIFT);
+            ACTION_VIRTUAL | (1 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_QUICK_SCRUB_PROGRESS =
-            ACTION_VIRTUAL | (3 << ACTION_POINTER_INDEX_SHIFT);
+            ACTION_VIRTUAL | (2 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_QUICK_SCRUB_END =
-            ACTION_VIRTUAL | (4 << ACTION_POINTER_INDEX_SHIFT);
+            ACTION_VIRTUAL | (3 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_RESET =
-            ACTION_VIRTUAL | (5 << ACTION_POINTER_INDEX_SHIFT);
+            ACTION_VIRTUAL | (4 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_DEFER_INIT =
+            ACTION_VIRTUAL | (5 << ACTION_POINTER_INDEX_SHIFT);
+    private static final int ACTION_SHOW_OVERVIEW_FROM_ALT_TAB =
             ACTION_VIRTUAL | (6 << ACTION_POINTER_INDEX_SHIFT);
 
     private final EventArray mEmptyArray = new EventArray();
@@ -143,9 +141,6 @@
                 MotionEvent event = array.get(i);
                 if (event.getActionMasked() == ACTION_VIRTUAL) {
                     switch (event.getAction()) {
-                        case ACTION_QUICK_SWITCH:
-                            mConsumer.updateTouchTracking(INTERACTION_QUICK_SWITCH);
-                            break;
                         case ACTION_QUICK_SCRUB_START:
                             mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB);
                             break;
@@ -161,6 +156,10 @@
                         case ACTION_DEFER_INIT:
                             mConsumer.deferInit();
                             break;
+                        case ACTION_SHOW_OVERVIEW_FROM_ALT_TAB:
+                            mConsumer.onShowOverviewFromAltTab();
+                            mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB);
+                            break;
                         default:
                             Log.e(TAG, "Invalid virtual event: " + event.getAction());
                     }
@@ -189,14 +188,14 @@
         queueNoPreProcess(MotionEvent.obtain(0, 0, action, progress, 0, 0));
     }
 
-    public void onQuickSwitch() {
-        queueVirtualAction(ACTION_QUICK_SWITCH, 0);
-    }
-
     public void onQuickScrubStart() {
         queueVirtualAction(ACTION_QUICK_SCRUB_START, 0);
     }
 
+    public void onOverviewShownFromAltTab() {
+        queueVirtualAction(ACTION_SHOW_OVERVIEW_FROM_ALT_TAB, 0);
+    }
+
     public void onQuickScrubProgress(float progress) {
         queueVirtualAction(ACTION_QUICK_SCRUB_PROGRESS, progress);
     }
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
deleted file mode 100644
index fca844c..0000000
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ /dev/null
@@ -1,393 +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) {
-        mIsGoingToHome = Float.compare(progress, 1) == 0;
-        ObjectAnimator anim = mCurrentShift.animateToValue(progress).setDuration(duration);
-        anim.setInterpolator(Interpolators.SCROLL);
-        anim.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                mStateCallback.setState(mIsGoingToHome
-                        ? STATE_SCALED_SNAPSHOT_RECENTS : STATE_SCALED_SNAPSHOT_APP);
-            }
-        });
-        anim.start();
-    }
-
-    @UiThread
-    private void resumeLastTask() {
-        RecentsTaskLoadPlan loadPlan = RecentsModel.getInstance(mContext).getLastLoadPlan();
-        if (loadPlan != null) {
-            Task task = loadPlan.getTaskStack().findTaskWithId(mRunningTaskId);
-            if (task != null) {
-                ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
-                ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(task.key, opts,
-                        null, null);
-            }
-        }
-    }
-
-    public void reset() {
-        mCurrentShift.cancelAnimation();
-        if (mGestureEndCallback != null) {
-            mGestureEndCallback.run();
-        }
-    }
-
-    private void cleanupLauncher() {
-        reset();
-
-        // TODO: These should be done as part of ActivityOptions#OnAnimationStarted
-        mLauncher.getStateManager().reapplyState();
-        mLauncher.setOnResumeCallback(() -> mDragView.close(false));
-    }
-
-    private void onAnimationToLauncherComplete() {
-        reset();
-
-        mDragView.close(false);
-        View currentRecentsPage = mRecentsView.getPageAt(mRecentsView.getCurrentPage());
-        if (currentRecentsPage instanceof TaskView) {
-            ((TaskView) currentRecentsPage).animateIconToScale(1f);
-        }
-        if (mInteractionType == INTERACTION_QUICK_SWITCH) {
-            if (mQuickScrubController != null) {
-                mQuickScrubController.onQuickSwitch();
-            }
-        }
-    }
-
-    public void onQuickScrubEnd() {
-        if (mQuickScrubController != null) {
-            mQuickScrubController.onQuickScrubEnd();
-        } else {
-            // TODO:
-        }
-    }
-
-    public void onQuickScrubProgress(float progress) {
-        if (mQuickScrubController != null) {
-            mQuickScrubController.onQuickScrubProgress(progress);
-        } else {
-            // TODO:
-        }
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index c96f6d7..4877abb 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -21,28 +21,20 @@
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.INVALID_POINTER_ID;
-
-import static com.android.quickstep.RemoteRunnable.executeSafely;
-import static com.android.quickstep.TouchInteractionService.DEBUG_SHOW_OVERVIEW_BUTTON;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Color;
-import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Looper;
-import android.util.Log;
+import android.os.SystemClock;
 import android.view.Choreographer;
 import android.view.Display;
 import android.view.MotionEvent;
@@ -52,9 +44,7 @@
 import android.view.WindowManager;
 
 import com.android.launcher3.MainThreadExecutor;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.util.TraceHelper;
-import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.AssistDataReceiver;
 import com.android.systemui.shared.system.BackgroundExecutor;
@@ -73,16 +63,15 @@
  */
 @TargetApi(Build.VERSION_CODES.P)
 public class OtherActivityTouchConsumer extends ContextWrapper implements TouchConsumer {
-    private static final String TAG = "ActivityTouchConsumer";
 
     private static final long LAUNCHER_DRAW_TIMEOUT_MS = 150;
-    private static final int[] DEFERRED_HIT_TARGETS = DEBUG_SHOW_OVERVIEW_BUTTON
+    private static final int[] DEFERRED_HIT_TARGETS = false
             ? new int[] {HIT_TARGET_BACK, HIT_TARGET_OVERVIEW} : new int[] {HIT_TARGET_BACK};
 
     private final RunningTaskInfo mRunningTask;
     private final RecentsModel mRecentsModel;
     private final Intent mHomeIntent;
-    private final ISystemUiProxy mISystemUiProxy;
+    private final ActivityControlHelper mActivityControlHelper;
     private final MainThreadExecutor mMainThreadExecutor;
     private final Choreographer mBackgroundThreadChoreographer;
 
@@ -93,7 +82,7 @@
     private boolean mTouchThresholdCrossed;
     private int mTouchSlop;
     private float mStartDisplacement;
-    private BaseSwipeInteractionHandler mInteractionHandler;
+    private WindowTransformSwipeHandler mInteractionHandler;
     private int mDisplayRotation;
     private Rect mStableInsets = new Rect();
 
@@ -102,7 +91,7 @@
     private boolean mIsGoingToHome;
 
     public OtherActivityTouchConsumer(Context base, RunningTaskInfo runningTaskInfo,
-            RecentsModel recentsModel, Intent homeIntent, ISystemUiProxy systemUiProxy,
+            RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
             MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer,
             @HitTarget int downHitTarget, VelocityTracker velocityTracker) {
         super(base);
@@ -110,13 +99,18 @@
         mRecentsModel = recentsModel;
         mHomeIntent = homeIntent;
         mVelocityTracker = velocityTracker;
-        mISystemUiProxy = systemUiProxy;
+        mActivityControlHelper = activityControl;
         mMainThreadExecutor = mainThreadExecutor;
         mBackgroundThreadChoreographer = backgroundThreadChoreographer;
         mIsDeferredDownTarget = Arrays.binarySearch(DEFERRED_HIT_TARGETS, downHitTarget) >= 0;
     }
 
     @Override
+    public void onShowOverviewFromAltTab() {
+        startTouchTrackingForWindowAnimation(SystemClock.uptimeMillis());
+    }
+
+    @Override
     public void accept(MotionEvent ev) {
         if (mVelocityTracker == null) {
             return;
@@ -132,7 +126,7 @@
 
                 // Start the window animation on down to give more time for launcher to draw if the
                 // user didn't start the gesture over the back button
-                if (!isUsingScreenShot() && !mIsDeferredDownTarget) {
+                if (!mIsDeferredDownTarget) {
                     startTouchTrackingForWindowAnimation(ev.getEventTime());
                 }
 
@@ -172,14 +166,11 @@
                     if (mTouchThresholdCrossed) {
                         mStartDisplacement = Math.signum(displacement) * mTouchSlop;
 
-                        if (isUsingScreenShot()) {
-                            startTouchTrackingForScreenshotAnimation();
-                        } else if (mIsDeferredDownTarget) {
+                        if (mIsDeferredDownTarget) {
                             // If we deferred starting the window animation on touch down, then
                             // start tracking now
                             startTouchTrackingForWindowAnimation(ev.getEventTime());
                         }
-
                         notifyGestureStarted();
                     }
                 } else if (mInteractionHandler != null) {
@@ -205,11 +196,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() {
@@ -220,69 +206,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);
@@ -299,8 +226,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) {
@@ -329,7 +255,7 @@
                             handler.onRecentsAnimationCanceled();
                         }
                     }
-                }, null, null);
+                });
 
         if (Looper.myLooper() != Looper.getMainLooper()) {
             startActivity.run();
@@ -358,7 +284,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();
@@ -375,7 +301,7 @@
     public void reset() {
         // Clean up the old interaction handler
         if (mInteractionHandler != null) {
-            final BaseSwipeInteractionHandler handler = mInteractionHandler;
+            final WindowTransformSwipeHandler handler = mInteractionHandler;
             mInteractionHandler = null;
             mIsGoingToHome = handler.mIsGoingToHome;
             mMainThreadExecutor.execute(handler::reset);
@@ -385,24 +311,15 @@
     @Override
     public void updateTouchTracking(int interactionType) {
         notifyGestureStarted();
-
-        if (isUsingScreenShot()) {
-            mMainThreadExecutor.execute(() -> {
-                if (mInteractionHandler != null) {
-                    mInteractionHandler.updateInteractionType(interactionType);
-                }
-            });
-        } else {
-            if (mInteractionHandler != null) {
-                mInteractionHandler.updateInteractionType(interactionType);
-            }
+        if (mInteractionHandler != null) {
+            mInteractionHandler.updateInteractionType(interactionType);
         }
     }
 
     @Override
     public Choreographer getIntrimChoreographer(MotionEventQueue queue) {
         mEventQueue = queue;
-        return isUsingScreenShot() ? null : mBackgroundThreadChoreographer;
+        return mBackgroundThreadChoreographer;
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index b60d1e2..311411f 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -17,22 +17,54 @@
 
 import static com.android.launcher3.LauncherState.OVERVIEW;
 
+import android.animation.Animator;
+import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityOptions;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.support.annotation.UiThread;
+import android.support.annotation.WorkerThread;
+import android.util.SparseArray;
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.states.InternalStateHandler;
+import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.ActivityControlHelper.FallbackActivityControllerHelper;
+import com.android.quickstep.ActivityControlHelper.LauncherActivityControllerHelper;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.AssistDataReceiver;
+import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import com.android.systemui.shared.system.RecentsAnimationListener;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * Helper class to handle various atomic commands for switching between Overview.
@@ -40,48 +72,263 @@
 @TargetApi(Build.VERSION_CODES.P)
 public class OverviewCommandHelper extends InternalStateHandler {
 
+    private static final int RID_RESET_SWIPE_HANDLER = 0;
+    private static final int RID_CANCEL_CONTROLLER = 1;
+    private static final int RID_CANCEL_ZOOM_OUT_ANIMATION = 2;
+
+    private static final long RECENTS_LAUNCH_DURATION = 150;
+
+    private static final String TAG = "OverviewCommandHelper";
+    private static final boolean DEBUG_START_FALLBACK_ACTIVITY = false;
+
     private final Context mContext;
     private final ActivityManagerWrapper mAM;
+    private final RecentsModel mRecentsModel;
+    private final MainThreadExecutor mMainThreadExecutor;
 
     public final Intent homeIntent;
     public final ComponentName launcher;
 
+    private final SparseArray<Runnable> mCurrentCommandFinishRunnables = new SparseArray<>();
+    // Monotonically increasing command ids.
+    private int mCurrentCommandId = 0;
+
     private long mLastToggleTime;
+    private WindowTransformSwipeHandler mWindowTransformSwipeHandler;
+
+    private final Point mWindowSize = new Point();
+    private final Rect mTaskTargetRect = new Rect();
+    private final RectF mTempTaskTargetRect = new RectF();
 
     public OverviewCommandHelper(Context context) {
         mContext = context;
         mAM = ActivityManagerWrapper.getInstance();
+        mMainThreadExecutor = new MainThreadExecutor();
+        mRecentsModel = RecentsModel.getInstance(mContext);
 
         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);
-        launcher = new ComponentName(context.getPackageName(), info.activityInfo.name);
+
+        if (DEBUG_START_FALLBACK_ACTIVITY) {
+            launcher = new ComponentName(context, RecentsActivity.class);
+            homeIntent.addCategory(Intent.CATEGORY_DEFAULT)
+                    .removeCategory(Intent.CATEGORY_HOME);
+        } else {
+            launcher = new ComponentName(context.getPackageName(), info.activityInfo.name);
+        }
+
         // Clear the packageName as system can fail to dedupe it b/64108432
         homeIntent.setComponent(launcher).setPackage(null);
     }
 
-    public void onOverviewToggle() {
-        long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
-        mLastToggleTime = SystemClock.elapsedRealtime();
+    private void openRecents() {
+        Intent intent = addToIntent(new Intent(homeIntent));
+        mContext.startActivity(intent);
+        initWhenReady();
+    }
 
-        if (isOverviewAlmostVisible()) {
-            boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
-            startNonLauncherTask(isQuickTap ? 2 : 1);
+    @UiThread
+    private void addFinishCommand(int requestId, int id, Runnable action) {
+        if (requestId < mCurrentCommandId) {
+            action.run();
         } else {
-            Intent intent = addToIntent(new Intent(homeIntent));
-            mContext.startActivity(intent);
-            initWhenReady();
+            mCurrentCommandFinishRunnables.put(id, action);
         }
     }
 
-    private void startNonLauncherTask(int backStackCount) {
-        for (RecentTaskInfo rti : mAM.getRecentTasks(backStackCount, UserHandle.myUserId())) {
-            backStackCount--;
-            if (backStackCount == 0) {
-                mAM.startActivityFromRecents(rti.id, null);
+    @UiThread
+    private void clearFinishCommand(int requestId, int id) {
+        if (requestId == mCurrentCommandId) {
+            mCurrentCommandFinishRunnables.remove(id);
+        }
+    }
+
+    @UiThread
+    private void initSwipeHandler(ActivityControlHelper helper, long time,
+            Consumer<WindowTransformSwipeHandler> onAnimationInitCallback) {
+        final int commandId = mCurrentCommandId;
+        RunningTaskInfo taskInfo = ActivityManagerWrapper.getInstance().getRunningTask();
+        final WindowTransformSwipeHandler handler =
+                new WindowTransformSwipeHandler(taskInfo, mContext, time, helper);
+
+        // Preload the plan
+        mRecentsModel.loadTasks(taskInfo.id, null);
+        mWindowTransformSwipeHandler = handler;
+
+        mTempTaskTargetRect.setEmpty();
+        handler.setGestureEndCallback(() -> {
+            if (mWindowTransformSwipeHandler == handler) {
+                mWindowTransformSwipeHandler = null;
+                mTempTaskTargetRect.setEmpty();
             }
+            clearFinishCommand(commandId, RID_RESET_SWIPE_HANDLER);
+            clearFinishCommand(commandId, RID_CANCEL_CONTROLLER);
+        });
+        handler.initWhenReady();
+        addFinishCommand(commandId, RID_RESET_SWIPE_HANDLER, handler::reset);
+
+        TraceHelper.beginSection(TAG);
+        Runnable startActivity = () -> helper.startRecents(mContext, homeIntent,
+                new AssistDataReceiver() {
+                    @Override
+                    public void onHandleAssistData(Bundle bundle) {
+                        mRecentsModel.preloadAssistData(taskInfo.id, bundle);
+                    }
+                },
+                new RecentsAnimationListener() {
+                    public void onAnimationStart(
+                            RecentsAnimationControllerCompat controller,
+                            RemoteAnimationTargetCompat[] apps, Rect homeContentInsets,
+                            Rect minimizedHomeBounds) {
+                        if (mWindowTransformSwipeHandler == handler) {
+                            TraceHelper.partitionSection(TAG, "Received");
+                            handler.onRecentsAnimationStart(controller, apps, homeContentInsets,
+                                    minimizedHomeBounds);
+                            mTempTaskTargetRect.set(handler.getTargetRect(mWindowSize));
+
+                            mMainThreadExecutor.execute(() -> {
+                                addFinishCommand(commandId,
+                                        RID_CANCEL_CONTROLLER, () -> controller.finish(true));
+                                if (commandId == mCurrentCommandId) {
+                                    onAnimationInitCallback.accept(handler);
+                                }
+                            });
+                        } else {
+                            TraceHelper.endSection(TAG, "Finishing no handler");
+                            controller.finish(false /* toHome */);
+                        }
+                    }
+
+                    public void onAnimationCanceled() {
+                        TraceHelper.endSection(TAG, "Cancelled: " + handler);
+                        if (mWindowTransformSwipeHandler == handler) {
+                            handler.onRecentsAnimationCanceled();
+                        }
+                    }
+                });
+
+        // We should almost always get touch-town on background thread. This is an edge case
+        // when the background Choreographer has not yet initialized.
+        BackgroundExecutor.get().submit(startActivity);
+    }
+
+    @UiThread
+    private void startZoomOutAnim(final WindowTransformSwipeHandler handler) {
+        final int commandId = mCurrentCommandId;
+        ValueAnimator anim = ValueAnimator.ofInt(0, -handler.getTransitionLength());
+        anim.addUpdateListener((a) -> handler.updateDisplacement((Integer) a.getAnimatedValue()));
+        anim.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                handler.onGestureEnded(0);
+                clearFinishCommand(commandId, RID_CANCEL_ZOOM_OUT_ANIMATION);
+            }
+        });
+        handler.onGestureStarted();
+        anim.setDuration(RECENTS_LAUNCH_DURATION);
+        anim.setInterpolator(Interpolators.AGGRESSIVE_EASE);
+        anim.start();
+        addFinishCommand(commandId, RID_CANCEL_ZOOM_OUT_ANIMATION, anim::cancel);
+    }
+
+    public void onOverviewToggle() {
+        long time = SystemClock.elapsedRealtime();
+        mMainThreadExecutor.execute(() -> {
+            long elapsedTime = time - mLastToggleTime;
+            mLastToggleTime = time;
+
+            mCurrentCommandId++;
+            mTempTaskTargetRect.round(mTaskTargetRect);
+            boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
+            int runnableCount = mCurrentCommandFinishRunnables.size();
+            if (runnableCount > 0) {
+                for (int i = 0; i < runnableCount; i++) {
+                    mCurrentCommandFinishRunnables.valueAt(i).run();
+                }
+                mCurrentCommandFinishRunnables.clear();
+                isQuickTap = true;
+            }
+
+            ActivityControlHelper helper = getActivityControlHelper();
+            RecentsView recents = helper.getVisibleRecentsView();
+            if (recents != null) {
+                int childCount = recents.getChildCount();
+                if (childCount != 0) {
+                    ((TaskView) recents.getChildAt(childCount >= 2 ? 1 : 0)).launchTask(true);
+                }
+
+                // There are not enough tasks. Skip
+                return;
+            }
+
+            if (isQuickTap) {
+                // Focus last task. Start is on background thread so that all ActivityManager calls
+                // are serialized
+                BackgroundExecutor.get().submit(this::startLastTask);
+                return;
+            }
+            if (helper.switchToRecentsIfVisible()) {
+                return;
+            }
+
+            initSwipeHandler(helper, time, this::startZoomOutAnim);
+        });
+    }
+
+    public void onOverviewShown() {
+        getLauncher().runOnUiThread(() -> {
+                    if (isOverviewAlmostVisible()) {
+                        final RecentsView rv = getLauncher().getOverviewPanel();
+                        rv.snapToTaskAfterNext();
+                    } else {
+                        openRecents();
+                    }
+                }
+        );
+    }
+
+    public void onOverviewHidden() {
+        getLauncher().runOnUiThread(() -> {
+                    if (isOverviewAlmostVisible()) {
+                        final RecentsView rv = getLauncher().getOverviewPanel();
+                        rv.launchNextTask();
+                    }
+                }
+        );
+    }
+
+    @WorkerThread
+    private void startLastTask() {
+        // TODO: This should go through recents model.
+        List<RecentTaskInfo> tasks = mAM.getRecentTasks(2, UserHandle.myUserId());
+        if (tasks.size() > 1) {
+            RecentTaskInfo rti = tasks.get(1);
+
+            final ActivityOptions options;
+            if (!mTaskTargetRect.isEmpty()) {
+                final Rect targetRect = new Rect(mTaskTargetRect);
+                targetRect.offset(Utilities.isRtl(mContext.getResources())
+                        ? - mTaskTargetRect.width() : mTaskTargetRect.width(), 0);
+                final AppTransitionAnimationSpecCompat specCompat =
+                        new AppTransitionAnimationSpecCompat(rti.id, null, targetRect);
+                AppTransitionAnimationSpecsFuture specFuture =
+                        new AppTransitionAnimationSpecsFuture(mMainThreadExecutor.getHandler()) {
+
+                    @Override
+                    public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+                        return Collections.singletonList(specCompat);
+                    }
+                };
+                options = RecentsTransition.createAspectScaleAnimation(mContext,
+                        mMainThreadExecutor.getHandler(), true /* scaleUp */,
+                        specFuture, () -> {});
+            } else {
+                options = ActivityOptions.makeBasic();
+            }
+            mAM.startActivityFromRecents(rti.id, options);
         }
     }
 
@@ -108,4 +355,15 @@
         return false;
     }
 
+    public boolean isUsingFallbackActivity() {
+        return DEBUG_START_FALLBACK_ACTIVITY;
+    }
+
+    public ActivityControlHelper getActivityControlHelper() {
+        if (DEBUG_START_FALLBACK_ACTIVITY) {
+            return new FallbackActivityControllerHelper();
+        } else {
+            return new LauncherActivityControllerHelper();
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 4af89bf..22b1757 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,20 +15,28 @@
  */
 package com.android.quickstep;
 
-import static com.android.quickstep.TouchInteractionService.DEBUG_SHOW_OVERVIEW_BUTTON;
+import static com.android.launcher3.Utilities.getPrefs;
+import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
+import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP;
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_HIDE_BACK_BUTTON;
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
 
 import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.support.annotation.WorkerThread;
 import android.util.Log;
 
+import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
+import java.util.concurrent.ExecutionException;
+
 /**
  * Sets overview interaction flags, such as:
  *
@@ -39,55 +47,109 @@
  *
  * @see com.android.systemui.shared.system.NavigationBarCompat.InteractionType and associated flags.
  */
-public class OverviewInteractionState {
+public class OverviewInteractionState implements OnSharedPreferenceChangeListener {
 
     private static final String TAG = "OverviewFlags";
-    private static final Handler sUiHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            updateOverviewInteractionFlag((Context) msg.obj, msg.what, msg.arg1 == 1);
-        }
-    };
-    private static final Handler sBackgroundHandler = new Handler(
-            UiThreadHelper.getBackgroundLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            ISystemUiProxy systemUiProxy = (ISystemUiProxy) msg.obj;
-            int flags = msg.what;
-            try {
-                systemUiProxy.setInteractionState(flags);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Unable to update overview interaction flags", e);
+
+    // We do not need any synchronization for this variable as its only written on UI thread.
+    private static OverviewInteractionState INSTANCE;
+
+    public static OverviewInteractionState getInstance(final Context context) {
+        if (INSTANCE == null) {
+            if (Looper.myLooper() == Looper.getMainLooper()) {
+                INSTANCE = new OverviewInteractionState(context.getApplicationContext());
+            } else {
+                try {
+                    return new MainThreadExecutor().submit(
+                            () -> OverviewInteractionState.getInstance(context)).get();
+                } catch (InterruptedException|ExecutionException e) {
+                    throw new RuntimeException(e);
+                }
             }
         }
-    };
-
-    private static int sFlags = DEBUG_SHOW_OVERVIEW_BUTTON ? FLAG_SHOW_OVERVIEW_BUTTON : 0;
-
-    public static void setBackButtonVisible(Context context, boolean visible) {
-        updateFlagOnUi(context, FLAG_HIDE_BACK_BUTTON, !visible);
+        return INSTANCE;
     }
 
-    private static void updateFlagOnUi(Context context, int flag, boolean enabled) {
-        sUiHandler.removeMessages(flag);
-        sUiHandler.sendMessage(sUiHandler.obtainMessage(flag, enabled ? 1 : 0, 0, context));
+    public 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 0ccd7f2..4f379e6 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -19,12 +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.Action.Touch;
 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.
@@ -34,7 +36,7 @@
  */
 public class QuickScrubController implements OnAlarmListener {
 
-    public static final int QUICK_SWITCH_START_DURATION = 210;
+    public static final int QUICK_SCRUB_START_DURATION = 210;
 
     private static final boolean ENABLE_AUTO_ADVANCE = true;
     private static final int NUM_QUICK_SCRUB_SECTIONS = 3;
@@ -45,17 +47,16 @@
 
     private final Alarm mAutoAdvanceAlarm;
     private final RecentsView mRecentsView;
-    private final Launcher mLauncher;
+    private final BaseActivity mActivity;
 
     private boolean mInQuickScrub;
     private int mQuickScrubSection;
     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();
@@ -68,11 +69,10 @@
         mStartedFromHome = startingFromHome;
         mQuickScrubSection = 0;
         mHasAlarmRun = false;
-        mQuickSwitched = false;
         mFinishedTransitionToQuickScrub = false;
 
         snapToNextTaskIfAvailable();
-        mLauncher.getUserEventDispatcher().resetActionDurationMillis();
+        mActivity.getUserEventDispatcher().resetActionDurationMillis();
     }
 
     public void onQuickScrubEnd() {
@@ -82,7 +82,13 @@
         }
         int page = mRecentsView.getNextPage();
         Runnable launchTaskRunnable = () -> {
-            ((TaskView) mRecentsView.getPageAt(page)).launchTask(true);
+            TaskView taskView = ((TaskView) mRecentsView.getPageAt(page));
+            if (taskView != null) {
+                taskView.launchTask(true);
+            } else {
+                // Break out of quick scrub so user can interact with launcher.
+                mActivity.onBackPressed();
+            }
         };
         int snapDuration = Math.abs(page - mRecentsView.getPageNearestToCenterOfScreen())
                 * QUICKSCRUB_END_SNAP_DURATION_PER_PAGE;
@@ -93,7 +99,7 @@
             // No page move needed, just launch it
             launchTaskRunnable.run();
         }
-        mLauncher.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
+        mActivity.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
                 ControlType.QUICK_SCRUB_BUTTON, null, mStartedFromHome ?
                         ContainerType.WORKSPACE : ContainerType.APP);
     }
@@ -117,45 +123,21 @@
         }
     }
 
-    public void onQuickSwitch() {
-        mQuickSwitched = true;
-        quickSwitchIfReady();
-    }
-
     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();
-            mLauncher.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);
+            mRecentsView.snapToPage(toPage, QUICK_SCRUB_START_DURATION);
         }
     }
 
     private void goToPageWithHaptic(int pageToGoTo) {
-        goToPageWithHaptic(pageToGoTo, -1);
-    }
-
-    private void goToPageWithHaptic(int pageToGoTo, int overrideDuration) {
         pageToGoTo = Utilities.boundToRange(pageToGoTo, 0, mRecentsView.getPageCount() - 1);
         if (pageToGoTo != mRecentsView.getNextPage()) {
-            int duration = overrideDuration > -1 ? overrideDuration
-                    : Math.abs(pageToGoTo - mRecentsView.getNextPage())
+            int duration = Math.abs(pageToGoTo - mRecentsView.getNextPage())
                             * QUICKSCRUB_SNAP_DURATION_PER_PAGE;
             mRecentsView.snapToPage(pageToGoTo, duration);
             mRecentsView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP,
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index f92d773..e579205 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -15,34 +15,88 @@
  */
 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);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        RecentsActivityTracker.onRecentsActivityDestroy(this);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
new file mode 100644
index 0000000..5bd606e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
@@ -0,0 +1,81 @@
+/*
+ * 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 static WeakReference<RecentsActivity> sCurrentActivity = 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();
+            }
+            sCurrentActivity = new WeakReference<>(activity);
+        }
+    }
+
+    public static void onRecentsActivityDestroy(RecentsActivity activity) {
+        synchronized (LOCK) {
+            if (sCurrentActivity.get() == activity) {
+                sCurrentActivity.clear();
+            }
+        }
+    }
+
+    public static RecentsActivity getCurrentActivity() {
+        synchronized (LOCK) {
+            return sCurrentActivity.get();
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index b4ce646..392b73f 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -210,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/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 768fbda..4e35159 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -29,21 +29,14 @@
 @FunctionalInterface
 public interface TouchConsumer extends Consumer<MotionEvent> {
 
-    static boolean isInteractionQuick(@InteractionType int interactionType) {
-        return interactionType == INTERACTION_QUICK_SCRUB ||
-                interactionType == INTERACTION_QUICK_SWITCH;
-    }
-
     @IntDef(flag = true, value = {
             INTERACTION_NORMAL,
-            INTERACTION_QUICK_SWITCH,
             INTERACTION_QUICK_SCRUB
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface InteractionType {}
     int INTERACTION_NORMAL = 0;
-    int INTERACTION_QUICK_SWITCH = 1;
-    int INTERACTION_QUICK_SCRUB = 2;
+    int INTERACTION_QUICK_SCRUB = 1;
 
     default void reset() { }
 
@@ -72,4 +65,6 @@
     default boolean forceToLauncherConsumer() {
         return false;
     }
+
+    default void onShowOverviewFromAltTab() {}
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 04740d9..cc49dc7 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -23,6 +23,7 @@
 import static android.view.MotionEvent.ACTION_UP;
 import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
@@ -47,8 +48,8 @@
 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;
@@ -60,7 +61,7 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class TouchInteractionService extends Service {
 
-    public static final boolean DEBUG_SHOW_OVERVIEW_BUTTON = false;
+    public static final boolean DEBUG_OPEN_OVERVIEW_VIA_ALT_TAB = false;
 
     private static final SparseArray<String> sMotionEventNames;
 
@@ -85,7 +86,7 @@
         @Override
         public void onPreMotionEvent(@HitTarget int downHitTarget) throws RemoteException {
             TraceHelper.beginSection("SysUiBinder");
-            onBinderPreMotionEvent(downHitTarget);
+            setupTouchConsumer(downHitTarget);
             TraceHelper.partitionSection("SysUiBinder", "Down target " + downHitTarget);
         }
 
@@ -105,15 +106,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);
-        }
-
-        @Override
-        public void onQuickSwitch() {
-            mEventQueue.onQuickSwitch();
-            TraceHelper.endSection("SysUiBinder", "onQuickSwitch");
+            mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
         }
 
         @Override
@@ -139,10 +132,29 @@
         }
 
         @Override
-        public void onOverviewShown(boolean triggeredFromAltTab) { }
+        public void onOverviewShown(boolean triggeredFromAltTab) {
+            if (DEBUG_OPEN_OVERVIEW_VIA_ALT_TAB) {
+                if (triggeredFromAltTab) {
+                    setupTouchConsumer(HIT_TARGET_NONE);
+                    mEventQueue.onOverviewShownFromAltTab();
+                }
+            }
+        }
 
         @Override
-        public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { }
+        public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+            if (DEBUG_OPEN_OVERVIEW_VIA_ALT_TAB) {
+                if (triggeredFromAltTab && !triggeredFromHomeKey) {
+                    // onOverviewShownFromAltTab initiates quick scrub. Ending it here.
+                    mEventQueue.onQuickScrubEnd();
+                }
+            }
+        }
+
+        @Override
+        public void onQuickStep(MotionEvent motionEvent) {
+
+        }
     };
 
     private final TouchConsumer mNoOpTouchConsumer = (ev) -> {};
@@ -159,6 +171,7 @@
     private MainThreadExecutor mMainThreadExecutor;
     private ISystemUiProxy mISystemUiProxy;
     private OverviewCommandHelper mOverviewCommandHelper;
+    private OverviewInteractionState mOverviewInteractionState;
 
     private Choreographer mMainThreadChoreographer;
     private Choreographer mBackgroundThreadChoreographer;
@@ -172,6 +185,7 @@
         mOverviewCommandHelper = new OverviewCommandHelper(this);
         mMainThreadChoreographer = Choreographer.getInstance();
         mEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
+        mOverviewInteractionState = OverviewInteractionState.getInstance(this);
 
         sConnected = true;
 
@@ -192,7 +206,7 @@
         return mMyBinder;
     }
 
-    private void onBinderPreMotionEvent(@HitTarget int downHitTarget) {
+    private void setupTouchConsumer(@HitTarget int downHitTarget) {
         mEventQueue.reset();
         TouchConsumer oldConsumer = mEventQueue.getConsumer();
         if (oldConsumer.deferNextEventToMainThread()) {
@@ -220,7 +234,8 @@
                 tracker = VelocityTracker.obtain();
             }
             return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel,
-                            mOverviewCommandHelper.homeIntent, mISystemUiProxy, mMainThreadExecutor,
+                            mOverviewCommandHelper.homeIntent,
+                            mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor,
                             mBackgroundThreadChoreographer, downHitTarget, tracker);
         }
     }
@@ -313,14 +328,10 @@
             if (mInvalidated) {
                 return;
             }
-            if (TouchConsumer.isInteractionQuick(interactionType)) {
+            if (interactionType == INTERACTION_QUICK_SCRUB) {
                 Runnable action = () -> {
-                    Runnable onComplete = null;
-                    if (interactionType == INTERACTION_QUICK_SWITCH) {
-                        onComplete = mQuickScrubController::onQuickSwitch;
-                    }
                     LauncherState fromState = mLauncher.getStateManager().getState();
-                    mLauncher.getStateManager().goToState(FAST_OVERVIEW, true, onComplete);
+                    mLauncher.getStateManager().goToState(FAST_OVERVIEW, true);
                     mQuickScrubController.onQuickScrubStart(fromState == NORMAL);
                 };
 
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 0d308a0..25f2f87 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.ACCEL_2;
 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.QuickScrubController.QUICK_SCRUB_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;
@@ -51,16 +44,15 @@
 import android.util.Log;
 import android.view.View;
 import android.view.ViewTreeObserver.OnDrawListener;
+import android.view.animation.Interpolator;
 
 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,8 +61,12 @@
 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;
@@ -82,42 +78,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;
 
@@ -140,9 +102,8 @@
 
     // States for quick switch/scrub
     private static final int STATE_SWITCH_TO_SCREENSHOT_COMPLETE = 1 << 10;
-    private static final int STATE_QUICK_SWITCH = 1 << 11;
-    private static final int STATE_QUICK_SCRUB_START = 1 << 12;
-    private static final int STATE_QUICK_SCRUB_END = 1 << 13;
+    private static final int STATE_QUICK_SCRUB_START = 1 << 11;
+    private static final int STATE_QUICK_SCRUB_END = 1 << 12;
 
     private static final int LAUNCHER_UI_STATES =
             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_ACTIVITY_MULTIPLIER_COMPLETE
@@ -191,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;
 
@@ -204,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;
 
@@ -230,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);
@@ -281,13 +250,10 @@
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
                 this::invalidateHandlerWithLauncher);
 
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_QUICK_SWITCH,
-                this::onQuickInteractionStart);
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_QUICK_SCRUB_START,
-                this::onQuickInteractionStart);
-
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SWITCH_TO_SCREENSHOT_COMPLETE
-                | STATE_QUICK_SWITCH, this::switchToFinalAppAfterQuickSwitch);
+                this::onQuickScrubStart);
+        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_QUICK_SCRUB_START
+                | STATE_SCALED_CONTROLLER_RECENTS, this::onFinishedTransitionToQuickScrub);
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SWITCH_TO_SCREENSHOT_COMPLETE
                 | STATE_QUICK_SCRUB_END, this::switchToFinalAppAfterQuickScrub);
     }
@@ -308,7 +274,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,
@@ -329,14 +296,15 @@
                 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;
-        }
+    public int getTransitionLength() {
+        return mTransitionDragLength;
+    }
+
+    public RectF getTargetRect(Point outWindowSize) {
+        outWindowSize.set(mDp.widthPx, mDp.heightPx);
+        return mInitialTargetRect;
     }
 
     private long getFadeInDuration() {
@@ -351,40 +319,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) {
@@ -392,31 +359,20 @@
         }
 
         mStateCallback.setState(STATE_LAUNCHER_STARTED);
-        LauncherState startState = mLauncher.getStateManager().getState();
-        if (startState.disableRestore) {
-            startState = mLauncher.getStateManager().getRestState();
-        }
-        mLauncher.getStateManager().setRestState(startState);
+        mActivityControlHelper.prepareRecentsUI(mActivity, mWasLauncherAlreadyVisible);
+        AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible);
 
-        AbstractFloatingView.closeAllOpenViews(mLauncher, 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() {
 
@@ -425,21 +381,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) {
@@ -447,7 +400,7 @@
     }
 
     private void launcherFrameDrawn() {
-        View rootView = mLauncher.getRootView();
+        View rootView = mActivity.getRootView();
         if (rootView.getAlpha() < 1) {
             if (mGestureStarted) {
                 final MultiStateCallback callback = mStateCallback;
@@ -466,19 +419,12 @@
     }
 
     private void initializeLauncherAnimationController() {
-        mLauncherLayoutListener.setHandler(this);
+        mLayoutListener.setHandler(this);
         onLauncherLayoutChanged();
 
         final long transitionDelay = mLauncherFrameDrawnTime - mTouchTimeMs;
-        // 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);
-        mMetricsLogger.write(builder);
+        SysuiEventLogger.writeDummyRecentsTransition(transitionDelay);
+
         if (LatencyTrackerCompat.isEnabled(mContext)) {
             LatencyTrackerCompat.logToggleRecents((int) transitionDelay);
         }
@@ -489,23 +435,16 @@
             throw new IllegalArgumentException(
                     "Can't change interaction type from " + mInteractionType);
         }
-        if (!isInteractionQuick(interactionType)) {
+        if (interactionType != INTERACTION_QUICK_SCRUB) {
             throw new IllegalArgumentException(
                     "Can't change interaction type to " + interactionType);
         }
         mInteractionType = interactionType;
 
-        setStateOnUiThread(interactionType == INTERACTION_QUICK_SWITCH
-                ? STATE_QUICK_SWITCH : STATE_QUICK_SCRUB_START);
+        setStateOnUiThread(STATE_QUICK_SCRUB_START);
 
         // Start the window animation without waiting for launcher.
-        animateToProgress(1f, QUICK_SWITCH_START_DURATION);
-    }
-
-    private void onQuickInteractionStart() {
-        mLauncher.getStateManager().goToState(FAST_OVERVIEW,
-                mWasLauncherAlreadyVisible || mGestureStarted);
-        mQuickScrubController.onQuickScrubStart(false);
+        animateToProgress(1f, QUICK_SCRUB_START_DURATION);
     }
 
     @WorkerThread
@@ -518,32 +457,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);
         }
     }
@@ -556,7 +477,12 @@
             if (mRecentsAnimationWrapper.controller != null) {
                 RectF currentRect;
                 synchronized (mTargetRect) {
-                    currentRect = mRectFEvaluator.evaluate(shift, mSourceRect, mTargetRect);
+                    Interpolator interpolator = mInteractionType == INTERACTION_QUICK_SCRUB
+                            ? ACCEL_2 : LINEAR;
+                    float interpolated = interpolator.getInterpolation(shift);
+                    currentRect = mRectFEvaluator.evaluate(interpolated, mSourceRect, mTargetRect);
+                    // Stay lined up with the center of the target, since it moves for quick scrub.
+                    currentRect.offset(mTargetRect.centerX() - currentRect.centerX(), 0);
                 }
 
                 mClipRect.left = (int) (mSourceWindowClipInsets.left * shift);
@@ -596,13 +522,18 @@
                 View firstTask = mRecentsView.getPageAt(0);
                 int scrollForFirstTask = mRecentsView.getScrollForPage(0);
                 int offsetFromFirstTask = (scrollForFirstTask - mRecentsView.getScrollX());
-                if (offsetFromFirstTask != 0) {
-                    synchronized (mTargetRect) {
-                        mTargetRect.set(mInitialTargetRect);
-                        Utilities.scaleRectFAboutCenter(mTargetRect, firstTask.getScaleX());
-                        float offsetX = offsetFromFirstTask + firstTask.getTranslationX();
-                        mTargetRect.offset(offsetX, 0);
-                    }
+                synchronized (mTargetRect) {
+                    mTargetRect.set(mInitialTargetRect);
+                    Utilities.scaleRectFAboutCenter(mTargetRect, firstTask.getScaleX());
+                    float offsetX = offsetFromFirstTask + firstTask.getTranslationX();
+                    float offsetY = mRecentsView.getTranslationY();
+                    mTargetRect.offset(offsetX, offsetY);
+                }
+                if (mRecentsAnimationWrapper.controller != null) {
+
+                    // TODO: This logic is spartanic!
+                    mRecentsAnimationWrapper.controller.setAnimationTargetsBehindSystemBars(
+                            shift < 0.12f);
                 }
             };
             if (Looper.getMainLooper() == Looper.myLooper()) {
@@ -650,14 +581,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);
     }
 
@@ -673,9 +603,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);
         }
     }
 
@@ -759,25 +690,20 @@
             mGestureEndCallback.run();
         }
 
-        clearReference();
+        mActivityInitListener.unregister();
         mInputConsumer.unregisterInputConsumer();
     }
 
     private void invalidateHandlerWithLauncher() {
         mLauncherTransitionController = null;
-        mLauncherLayoutListener.setHandler(null);
-        mLauncherLayoutListener.close(false);
-
-        // Restore the requested orientation to the user preference after the gesture has ended
-        mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_NONE);
+        mLayoutListener.finish();
 
         mRecentsView.setFirstTaskIconScaledDown(false /* isScaledDown */, false /* animate */);
     }
 
     private void resetStateForAnimationCancel() {
-        LauncherState startState = mLauncher.getStateManager().getRestState();
-        boolean animate = mWasLauncherAlreadyVisible || mGestureStarted;
-        mLauncher.getStateManager().goToState(startState, animate);
+        boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
+        mActivityControlHelper.onTransitionCancelled(mActivity, wasVisible);
     }
 
     public void layoutListenerClosed() {
@@ -806,17 +732,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;
                         }
                     }
@@ -832,8 +749,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 */);
@@ -841,21 +757,13 @@
         reset();
     }
 
-    public void onQuickScrubEnd() {
-        setStateOnUiThread(STATE_QUICK_SCRUB_END);
+    private void onQuickScrubStart() {
+        mActivityControlHelper.onQuickInteractionStart(mActivity, mWasLauncherAlreadyVisible);
+        mQuickScrubController.onQuickScrubStart(false);
     }
 
-    private void switchToFinalAppAfterQuickSwitch() {
-        mQuickScrubController.onQuickSwitch();
-    }
-
-    private void switchToFinalAppAfterQuickScrub() {
-        mQuickScrubController.onQuickScrubEnd();
-
-        // Normally this is handled in reset(), but since we are still scrubbing after the
-        // transition into recents, we need to defer the handler invalidation for quick scrub until
-        // after the gesture ends
-        setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+    private void onFinishedTransitionToQuickScrub() {
+        mQuickScrubController.onFinishedTransitionToQuickScrub();
     }
 
     public void onQuickScrubProgress(float progress) {
@@ -868,6 +776,19 @@
         mQuickScrubController.onQuickScrubProgress(progress);
     }
 
+    public void onQuickScrubEnd() {
+        setStateOnUiThread(STATE_QUICK_SCRUB_END);
+    }
+
+    private void switchToFinalAppAfterQuickScrub() {
+        mQuickScrubController.onQuickScrubEnd();
+
+        // Normally this is handled in reset(), but since we are still scrubbing after the
+        // transition into recents, we need to defer the handler invalidation for quick scrub until
+        // after the gesture ends
+        setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+    }
+
     private void debugNewState(int stateFlag) {
         if (!DEBUG_STATES) {
             return;
@@ -887,4 +808,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..130d34c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -0,0 +1,199 @@
+/*
+ * 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.annotation.TargetApi;
+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.os.Build;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+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
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class LauncherRecentsView extends RecentsView<Launcher> implements Insettable {
+
+    public static final FloatProperty<LauncherRecentsView> TRANSLATION_X_FACTOR =
+            new FloatProperty<LauncherRecentsView>("translationXFactor") {
+
+                @Override
+                public void setValue(LauncherRecentsView view, float v) {
+                    view.setTranslationXFactor(v);
+                }
+
+                @Override
+                public Float get(LauncherRecentsView view) {
+                    return view.mTranslationXFactor;
+                }
+            };
+
+    public static final FloatProperty<LauncherRecentsView> TRANSLATION_Y_FACTOR =
+            new FloatProperty<LauncherRecentsView>("translationYFactor") {
+
+                @Override
+                public void setValue(LauncherRecentsView view, float v) {
+                    view.setTranslationYFactor(v);
+                }
+
+                @Override
+                public Float get(LauncherRecentsView view) {
+                    return view.mTranslationYFactor;
+                }
+            };
+
+    private Bitmap mScrim;
+    private Paint mFadePaint;
+    private Shader mFadeShader;
+    private Matrix mFadeMatrix;
+    private boolean mScrimOnLeft;
+
+    private float mTranslationXFactor;
+    private float mTranslationYFactor;
+    private Rect mPagePadding = new Rect();
+
+    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);
+        mPagePadding.set(padding);
+        mPagePadding.top += getResources().getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+
+        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);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        int width = right - left;
+        setTranslationX(mTranslationXFactor * (mIsRtl ? -width : width));
+        setTranslationYFactor(mTranslationYFactor);
+    }
+
+    public void setTranslationXFactor(float translationFactor) {
+        mTranslationXFactor = translationFactor;
+        setTranslationX(translationFactor * (mIsRtl ? -getWidth() : getWidth()));
+    }
+
+    public float getTranslationXFactor() {
+        return mTranslationXFactor;
+    }
+
+    public void setTranslationYFactor(float translationFactor) {
+        mTranslationYFactor = translationFactor;
+        setTranslationY(mTranslationYFactor * (mPagePadding.bottom - mPagePadding.top));
+    }
+
+    public float getTranslationYFactor() {
+        return mTranslationYFactor;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
similarity index 64%
rename from quickstep/src/com/android/quickstep/RecentsView.java
rename to quickstep/src/com/android/quickstep/views/RecentsView.java
index 3cd1179..db82286 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -14,42 +14,38 @@
  * limitations under the License.
  */
 
-package com.android.quickstep;
+package com.android.quickstep.views;
 
-import static com.android.launcher3.LauncherState.NORMAL;
+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.LayoutTransition;
-import android.animation.LayoutTransition.TransitionListener;
+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.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.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 android.view.ViewGroup;
-import android.widget.FrameLayout;
 
+import com.android.launcher3.BaseActivity;
 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.RecentsViewStateController;
+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;
@@ -64,26 +60,27 @@
 /**
  * A list of recent tasks.
  */
-public class RecentsView extends PagedView implements Insettable, OnSharedPreferenceChangeListener {
-
-    private static final Rect sTempStableInsets = new Rect();
+@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 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 static final Rect sTempStableInsets = new Rect();
 
-    private float mFastFlingVelocity;
+    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 TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+    private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
         public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
             for (int i = 0; i < getChildCount(); i++) {
@@ -96,40 +93,32 @@
         }
     };
 
-    private RecentsViewStateController mStateController;
-
-    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);
-    }
+    private boolean mOverviewStateEnabled;
+    private boolean mTaskStackListenerRegistered;
+    private Runnable mNextPageSwitchRunnable;
 
-    public RecentsView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
+    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);
-        setupLayoutTransition();
         setClipToOutline(true);
 
-        mLauncher = Launcher.getLauncher(context);
-        mQuickScrubController = new QuickScrubController(mLauncher, this);
+        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);
@@ -162,40 +151,6 @@
         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();
-        mFastFlingVelocity = res.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
-    }
-
     @Override
     protected void onWindowVisibilityChanged(int visibility) {
         super.onWindowVisibilityChanged(visibility);
@@ -217,39 +172,16 @@
     }
 
     @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);
+    public void onViewRemoved(View child) {
+        super.onViewRemoved(child);
 
-        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;
+        // 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);
         }
     }
 
@@ -268,14 +200,6 @@
         return null;
     }
 
-    public void setStateController(RecentsViewStateController stateController) {
-        mStateController = stateController;
-    }
-
-    public RecentsViewStateController getStateController() {
-        return mStateController;
-    }
-
     public void setOverviewStateEnabled(boolean enabled) {
         mOverviewStateEnabled = enabled;
         updateTaskStackListenerState();
@@ -295,7 +219,10 @@
     }
 
     private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
-        final RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+        if (mPendingAnimation != null) {
+            mPendingAnimation.addEndListener((b) -> applyLoadPlan(loadPlan));
+            return;
+        }
         TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
         if (stack == null) {
             removeAllViews();
@@ -308,7 +235,6 @@
         // necessary)
         final LayoutInflater inflater = LayoutInflater.from(getContext());
         final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
-        setLayoutTransition(null);
 
         final int requiredChildCount = tasks.size();
         for (int i = getChildCount(); i < requiredChildCount; i++) {
@@ -317,12 +243,11 @@
         }
         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);
+
+        // Unload existing visible task data
+        unloadVisibleTaskData();
 
         // Rebind and reset all task views
         for (int i = requiredChildCount - 1; i >= 0; i--) {
@@ -330,12 +255,8 @@
             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();
+        resetTaskVisuals();
         applyIconScale(false /* animate */);
 
         if (oldChildCount != getChildCount()) {
@@ -343,6 +264,19 @@
         }
     }
 
+    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;
@@ -359,7 +293,7 @@
         }
     }
 
-    private static Rect getPadding(DeviceProfile profile, Context context) {
+    protected static Rect getPadding(DeviceProfile profile, Context context) {
         WindowManagerWrapper.getInstance().getStableInsets(sTempStableInsets);
         Rect padding = new Rect(profile.workspacePadding);
 
@@ -421,7 +355,7 @@
 
         // Update the high res thumbnail loader
         RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
-//        loader.getHighResThumbnailLoader().setFlingingFast(isFlingingFast);
+        loader.getHighResThumbnailLoader().setFlingingFast(isFlingingFast);
         return scrolling;
     }
 
@@ -440,7 +374,7 @@
         final int pageCount = getPageCount();
         for (int i = 0; i < pageCount; i++) {
             View page = getPageAt(i);
-            int pageCenter = page.getLeft() + halfPageWidth;
+            float pageCenter = page.getLeft() + page.getTranslationX() + halfPageWidth;
             float distanceFromScreenCenter = screenCenter - pageCenter;
             float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
             mScrollState.linearInterpolation = Math.min(1,
@@ -453,47 +387,59 @@
      * 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() {
+    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);
-        for (int i = 0; i < getChildCount(); i++) {
+        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 (!mPrevVisibleTasks.get(i)) {
+                if (!mHasVisibleTaskData.get(task.key.id)) {
                     loader.loadTaskData(task);
-//                    loader.getHighResThumbnailLoader().onTaskVisible(task);
+                    loader.getHighResThumbnailLoader().onTaskVisible(task);
                 }
+                mHasVisibleTaskData.put(task.key.id, visible);
             } else {
-                if (mPrevVisibleTasks.get(i)) {
+                if (mHasVisibleTaskData.get(task.key.id)) {
                     loader.unloadTaskData(task);
-//                    loader.getHighResThumbnailLoader().onTaskInvisible(task);
+                    loader.getHighResThumbnailLoader().onTaskInvisible(task);
                 }
+                mHasVisibleTaskData.delete(task.key.id);
             }
-            mPrevVisibleTasks.put(i, visible);
         }
     }
 
-    public void onTaskDismissed(TaskView taskView) {
-        ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
-        removeView(taskView);
-        if (getChildCount() == 0) {
-            mLauncher.getStateManager().goToState(NORMAL);
+    /**
+     * 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);
     }
 
-    public int getRunningTaskId() {
-        return mRunningTaskId;
-    }
-
     /**
      * Reloads the view if anything in recents changed.
      */
@@ -516,11 +462,9 @@
         if (getChildCount() == 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, 0);
-            setLayoutTransition(mLayoutTransition);
         }
         mRunningTaskId = runningTaskId;
         setCurrentPage(0);
@@ -559,35 +503,6 @@
         }
     }
 
-    @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 {
 
         /**
@@ -604,4 +519,134 @@
          */
         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);
+    }
+
+    private void snapToPageRelative(int delta) {
+        snapToPage((getNextPage() + getPageCount() + delta) % getPageCount());
+    }
+
+    @Override
+    public void onVisibilityAggregated(boolean isVisible) {
+        super.onVisibilityAggregated(isVisible);
+        if (isVisible && !isFocused()) {
+            // Having focus, even in touch mode, keeps us from losing [Alt+]Tab by preventing
+            // switching to keyboard mode.
+            requestFocus();
+        }
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (event.getAction() == KeyEvent.ACTION_DOWN) {
+            switch (event.getKeyCode()) {
+                case KeyEvent.KEYCODE_TAB:
+                    snapToPageRelative(event.isShiftPressed() ? -1 : 1);
+                    return true;
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    snapToPageRelative(mIsRtl ? -1 : 1);
+                    return true;
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                    snapToPageRelative(mIsRtl ? 1 : -1);
+                    return true;
+            }
+        }
+        return super.dispatchKeyEvent(event);
+    }
+
+    public void snapToTaskAfterNext() {
+        snapToPageRelative(1);
+    }
+
+    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 2cdd4e9..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;
@@ -152,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 89%
rename from quickstep/src/com/android/quickstep/TaskView.java
rename to quickstep/src/com/android/quickstep/views/TaskView.java
index e0b03b8..7de1600 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.quickstep;
+package com.android.quickstep.views;
 
 import android.animation.TimeInterpolator;
 import android.app.ActivityOptions;
@@ -28,10 +28,10 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseDraggingActivity;
 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;
@@ -54,6 +54,11 @@
      */
     private static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
 
+    /**
+     * How much to scale down pages near the edge of the screen.
+     */
+    private static final float EDGE_SCALE_DOWN_FACTOR = 0.03f;
+
     private static final long SCALE_ICON_DURATION = 120;
 
     private Task mTask;
@@ -110,7 +115,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);
             }
@@ -154,6 +160,7 @@
         setScaleY(1f);
         setTranslationX(0f);
         setTranslationY(0f);
+        setTranslationZ(0);
         setAlpha(1f);
     }
 
@@ -163,6 +170,10 @@
                 CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
 
         mSnapshotView.setDimAlpha(1 - curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
+
+        float scale = 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
+        setScaleX(scale);
+        setScaleY(scale);
     }
 
     private static final class TaskOutlineProvider extends ViewOutlineProvider {
diff --git a/res/drawable/ic_drag_indicator.xml b/res/drawable/ic_drag_indicator.xml
new file mode 100644
index 0000000..d50bdd3
--- /dev/null
+++ b/res/drawable/ic_drag_indicator.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:height="2dp"
+        android:width="16dp"
+        android:viewportHeight="2.0"
+        android:viewportWidth="16.0">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M1,0h14c0.55,0,1,0.45,1,1s-0.45,1-1,1H1C0.45,2,0,1.55,0,1S0.45,0,1,0z"/>
+</vector>
\ No newline at end of file
diff --git a/res/layout/all_apps_fast_scroller.xml b/res/layout/all_apps_fast_scroller.xml
index 5537bc6..d858d3e 100644
--- a/res/layout/all_apps_fast_scroller.xml
+++ b/res/layout/all_apps_fast_scroller.xml
@@ -21,8 +21,8 @@
         android:id="@+id/fast_scroller_popup"
         style="@style/FastScrollerPopup"
         android:layout_alignParentEnd="true"
-        android:layout_below="@+id/search_container_all_apps"
-        android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
+        android:layout_marginEnd="@dimen/fastscroll_popup_margin"
+        android:layout_marginTop="@dimen/all_apps_search_bar_field_height_and_margin" />
 
     <com.android.launcher3.views.RecyclerViewFastScroller
         android:id="@+id/fast_scroller"
@@ -30,8 +30,8 @@
         android:layout_height="wrap_content"
         android:layout_alignParentBottom="true"
         android:layout_alignParentEnd="true"
-        android:layout_below="@+id/search_container_all_apps"
         android:layout_marginEnd="@dimen/fastscroll_end_margin"
+        android:layout_marginTop="@dimen/all_apps_search_bar_field_height_and_margin"
         launcher:canThumbDetach="true" />
 
 </merge>
\ No newline at end of file
diff --git a/res/layout/all_apps_floating_header.xml b/res/layout/all_apps_floating_header.xml
index c4240f8..f88c600 100644
--- a/res/layout/all_apps_floating_header.xml
+++ b/res/layout/all_apps_floating_header.xml
@@ -18,10 +18,10 @@
     android:id="@+id/all_apps_header"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:layout_below="@id/search_container_all_apps"
+    android:layout_marginTop="@dimen/all_apps_search_bar_field_height_and_margin"
     android:clipToPadding="false"
-    android:paddingTop="@dimen/all_apps_header_top_padding"
-    android:orientation="vertical" >
+    android:orientation="vertical"
+    android:paddingTop="@dimen/all_apps_header_top_padding" >
 
     <com.android.launcher3.allapps.PersonalWorkSlidingTabStrip
         android:id="@+id/tabs"
diff --git a/res/layout/all_apps_rv_layout.xml b/res/layout/all_apps_rv_layout.xml
index c353b36..8eba7fe 100644
--- a/res/layout/all_apps_rv_layout.xml
+++ b/res/layout/all_apps_rv_layout.xml
@@ -19,7 +19,7 @@
     android:id="@+id/apps_list_view"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:layout_below="@id/search_container_all_apps"
+    android:layout_marginTop="@dimen/all_apps_search_bar_field_height_and_margin"
     android:clipToPadding="false"
     android:descendantFocusability="afterDescendants"
     android:focusable="true" />
diff --git a/res/layout/all_apps_tabs.xml b/res/layout/all_apps_tabs.xml
index 2accd2d..fea2eea 100644
--- a/res/layout/all_apps_tabs.xml
+++ b/res/layout/all_apps_tabs.xml
@@ -20,9 +20,8 @@
     android:id="@+id/all_apps_tabs_view_pager"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:layout_below="@id/search_container_all_apps"
     android:layout_gravity="center_horizontal|top"
-    android:layout_marginTop="@dimen/all_apps_header_tab_height"
+    android:layout_marginTop="@dimen/all_apps_tabs_top_margin"
     android:clipChildren="true"
     android:clipToPadding="false"
     android:descendantFocusability="afterDescendants"
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..b7e7ca1 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 -->
@@ -99,7 +95,12 @@
 
     <dimen name="all_apps_divider_margin_vertical">8dp</dimen>
 
-<!-- Widget tray -->
+    <!-- Derived dimens -->
+    <dimen name="all_apps_search_bar_field_height_and_margin">56dp</dimen>
+    <!-- all_apps_search_bar_field_height_and_margin + all_apps_header_tab_height -->
+    <dimen name="all_apps_tabs_top_margin">106dp</dimen>
+
+ <!-- Widget tray -->
     <dimen name="widget_preview_label_vertical_padding">8dp</dimen>
     <dimen name="widget_preview_label_horizontal_padding">16dp</dimen>
 
@@ -227,5 +228,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..7d5d81c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -72,6 +72,11 @@
     <string name="notifications_header">Notifications</string>
 
     <!-- Drag and drop -->
+    <!-- Message to tell the user to press and hold on a shortcut to add it [CHAR_LIMIT=50] -->
+    <string name="long_press_shortcut_to_add">Touch &amp; hold to pick up a shortcut.</string>
+    <!-- Accessibility spoken hint message in deep shortcut menu, which allows user to add a shortcut. Custom action is the label for additional accessibility actions available in this mode [CHAR_LIMIT=100] -->
+    <string name="long_accessible_way_to_add_shortcut">Double-tap &amp; hold to pick up a shortcut or use custom actions.</string>
+
     <skip />
     <!-- Error message when user has filled a home screen -->
     <string name="out_of_space">No more room on this Home screen.</string>
@@ -80,6 +85,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 +177,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 +201,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 +286,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 +335,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..38b5dae 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -112,9 +112,9 @@
         <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>
+        <item name="android:defaultFocusHighlightEnabled">false</item>
 
         <!-- No shadows in the base theme -->
         <item name="android:shadowRadius">0</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/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/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..ed94aa43 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,16 @@
      */
     private PendingRequestArgs mPendingRequestArgs;
 
-    private final PointF mLastDispatchTouchEvent = new PointF();
-
     public ViewGroupFocusHelper mFocusHandler;
     private boolean mAppLaunchSuccess;
 
     private RotationHelper mRotationHelper;
-    private ActionMode mCurrentActionMode;
+
+    // Used to keep track of the swipe up state
+    private SharedPreferences.OnSharedPreferenceChangeListener mSharedPrefsListener =
+            (sharedPreferences, s) -> {
+                mDragLayer.setup(mDragController);
+            };
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -300,7 +279,7 @@
         initDeviceProfile(app.getInvariantDeviceProfile());
 
         mSharedPrefs = Utilities.getPrefs(this);
-        mIsSafeModeEnabled = getPackageManager().isSafeMode();
+        mSharedPrefs.registerOnSharedPreferenceChangeListener(mSharedPrefsListener);
         mIconCache = app.getIconCache();
         mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);
 
@@ -392,6 +371,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 +393,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 +410,6 @@
         return mStateManager;
     }
 
-    public LauncherAppTransitionManager getAppTransitionManager() {
-        return mAppTransitionManager;
-    }
-
     protected void overrideTheme(boolean isDark, boolean supportsDarkText) {
         if (isDark) {
             setTheme(R.style.LauncherThemeDark);
@@ -500,6 +472,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.
@@ -777,6 +765,7 @@
         }
         NotificationListener.removeNotificationsChangedListener();
         getStateManager().moveToRestState();
+
     }
 
     @Override
@@ -784,10 +773,6 @@
         super.onStart();
         FirstFrameAnimatorHelper.setIsVisible(true);
 
-        if (mOnStartCallback != null) {
-            mOnStartCallback.onLauncherStart(this);
-            mOnStartCallback = null;
-        }
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onStart();
         }
@@ -952,23 +937,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 +963,7 @@
         mDragController.setMoveTarget(mWorkspace);
         mDropTargetBar.setup(mDragController);
 
-        mAllAppsController.setupViews(mAppsView, mHotseat);
+        mAllAppsController.setupViews(mAppsView);
     }
 
     /**
@@ -1202,10 +1179,12 @@
         return mAllAppsController;
     }
 
+    @Override
     public LauncherRootView getRootView() {
         return (LauncherRootView) mLauncherView;
     }
 
+    @Override
     public DragLayer getDragLayer() {
         return mDragLayer;
     }
@@ -1222,7 +1201,7 @@
         return mHotseat;
     }
 
-    public <T extends ViewGroup> T getOverviewPanel() {
+    public <T extends View> T getOverviewPanel() {
         return (T) mOverviewPanel;
     }
 
@@ -1282,11 +1261,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()) {
@@ -1365,6 +1348,7 @@
             LauncherAppState.getInstance(this).setLauncher(null);
         }
         mRotationHelper.destroy();
+        mSharedPrefs.unregisterOnSharedPreferenceChangeListener(mSharedPrefsListener);
 
         try {
             mAppWidgetHost.stopListening();
@@ -1659,51 +1643,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 +1682,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 +1783,6 @@
         mOnResumeCallback = callback;
     }
 
-    public void setOnStartCallback(OnStartCallback callback) {
-        mOnStartCallback = callback;
-    }
-
     /**
      * Implementation of the method from LauncherModel.Callbacks.
      */
@@ -2628,8 +2433,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 +2447,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 +2454,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/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index acad901..8b7ba20 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -60,7 +60,7 @@
 import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.provider.RestoreDbTask;
-import com.android.launcher3.util.NoLocaleSqliteContext;
+import com.android.launcher3.util.NoLocaleSQLiteHelper;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.Thunk;
 
@@ -546,7 +546,7 @@
     /**
      * The class is subclassed in tests to create an in-memory db.
      */
-    public static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback {
+    public static class DatabaseHelper extends NoLocaleSQLiteHelper implements LayoutParserCallback {
         private final Handler mWidgetHostResetHandler;
         private final Context mContext;
         private long mMaxItemId = -1;
@@ -572,7 +572,7 @@
          */
         public DatabaseHelper(
                 Context context, Handler widgetHostResetHandler, String tableName) {
-            super(new NoLocaleSqliteContext(context), tableName, null, SCHEMA_VERSION);
+            super(context, tableName, SCHEMA_VERSION);
             mContext = context;
             mWidgetHostResetHandler = widgetHostResetHandler;
         }
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..b1bf6ec 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -40,6 +40,18 @@
  */
 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_ICONS = 1 << 0;
+    public static final int HOTSEAT_EXTRA = 1 << 1; // e.g. a search box
+    public static final int ALL_APPS_HEADER = 1 << 2;
+    public static final int ALL_APPS_HEADER_EXTRA = 1 << 3; // e.g. app predictions
+    public static final int ALL_APPS_CONTENT = 1 << 4;
+
     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;
@@ -50,7 +62,7 @@
     protected static final int FLAG_ALL_APPS_SCRIM = 1 << 7;
     protected static final int FLAG_DISABLE_INTERACTION = 1 << 8;
     protected static final int FLAG_OVERVIEW_UI = 1 << 9;
-
+    protected static final int FLAG_HIDE_BACK_BUTTON = 1 << 10;
 
     protected static final PageAlphaProvider DEFAULT_ALPHA_PROVIDER =
             new PageAlphaProvider(ACCEL_2) {
@@ -66,15 +78,15 @@
      * TODO: Create a separate class for NORMAL state.
      */
     public static final LauncherState NORMAL = new LauncherState(0, ContainerType.WORKSPACE,
-            0, FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED);
+            0, FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_HIDE_BACK_BUTTON);
 
-    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;
 
@@ -131,6 +143,12 @@
      */
     public final boolean overviewUi;
 
+    /**
+     * True if the back button should be hidden when in this state (assuming no floating views are
+     * open, launcher has window focus, etc).
+     */
+    public final boolean hideBackButton;
+
     public LauncherState(int id, int containerType, int transitionDuration, int flags) {
         this.containerType = containerType;
         this.transitionDuration = transitionDuration;
@@ -148,6 +166,7 @@
         this.disablePageClipping = (flags & FLAG_DISABLE_PAGE_CLIPPING) != 0;
         this.disableInteraction = (flags & FLAG_DISABLE_INTERACTION) != 0;
         this.overviewUi = (flags & FLAG_OVERVIEW_UI) != 0;
+        this.hideBackButton = (flags & FLAG_HIDE_BACK_BUTTON) != 0;
 
         this.ordinal = id;
         sAllStates[id] = this;
@@ -161,8 +180,13 @@
         return new float[] {1, 0, 0};
     }
 
-    public float getHoseatAlpha(Launcher launcher) {
-        return 1f;
+    /**
+     * Returns 2 floats designating how much to translate overview:
+     *   X factor is based on width, e.g. 0 is fully onscreen and 1 is fully offscreen
+     *   Y factor is based on padding, e.g. 0 is top aligned and 0.5 is centered vertically
+     */
+    public float[] getOverviewTranslationFactor(Launcher launcher) {
+        return new float[] {1f, 0f};
     }
 
     public void onStateEnabled(Launcher launcher) {
@@ -175,6 +199,13 @@
         return launcher.getWorkspace();
     }
 
+    public int getVisibleElements(Launcher launcher) {
+        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
+            return HOTSEAT_ICONS;
+        }
+        return HOTSEAT_ICONS | HOTSEAT_EXTRA;
+    }
+
     /**
      * 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 950a8ac..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);
     }
 
     /**
@@ -183,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);
 
@@ -237,7 +245,6 @@
 
     protected AnimatorSet createAnimationToNewWorkspaceInternal(final LauncherState state,
             AnimatorSetBuilder builder, final Runnable onCompleteRunnable) {
-        preOnStateTransitionStart();
 
         for (StateHandler handler : getStateHandlers()) {
             builder.startTag(handler);
@@ -251,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);
                 }
             }
 
@@ -277,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;
@@ -351,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;
@@ -376,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);
@@ -390,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..3a222c2 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -18,79 +18,25 @@
 
 import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
+import static com.android.launcher3.LauncherState.HOTSEAT_EXTRA;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.util.Property;
 import android.view.View;
 
 import com.android.launcher3.LauncherState.PageAlphaProvider;
 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);
-    }
-}
+import com.android.launcher3.uioverrides.UiFactory;
 
 /**
  * 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 +53,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 +79,17 @@
         propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
                 scaleAndTranslation[2], Interpolators.ZOOM_IN);
 
-        propertySetter.setViewAlpha(mLauncher.getHotseat(), state.getHoseatAlpha(mLauncher),
+        int elements = state.getVisibleElements(mLauncher);
+        float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
+        float hotseatExtraAlpha = (elements & HOTSEAT_EXTRA) != 0 ? 1 : 0;
+        propertySetter.setViewAlpha(mLauncher.getHotseat().getLayout(), hotseatIconsAlpha,
                 pageAlphaProvider.interpolator);
+        for (View hotseatExtraContent : UiFactory.getHotseatExtraContent(mLauncher.getHotseat())) {
+            propertySetter.setViewAlpha(hotseatExtraContent, hotseatExtraAlpha,
+                    pageAlphaProvider.interpolator);
+        }
         propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
-                state.getHoseatAlpha(mLauncher), pageAlphaProvider.interpolator);
+                hotseatIconsAlpha, 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..ae41794 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,19 @@
         return mHeader;
     }
 
+    public View getSearchView() {
+        return mSearchContainer;
+    }
+
+    public View getContentView() {
+        return mViewPager == null ? getActiveRecyclerView() : mViewPager;
+    }
+
+    public RecyclerViewFastScroller getScrollBar() {
+        AllAppsRecyclerView rv = getActiveRecyclerView();
+        return rv == null ? null : rv.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..9be123f 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -1,7 +1,12 @@
 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.LauncherState.ALL_APPS_HEADER_EXTRA;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
 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 +18,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;
 
 /**
@@ -52,10 +56,7 @@
         }
     };
 
-    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;
@@ -170,10 +168,24 @@
         ObjectAnimator anim =
                 ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, mProgress, targetProgress);
         anim.setDuration(config.duration);
-        anim.setInterpolator(interpolator);
+        anim.setInterpolator(builder.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator));
         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 hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 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(hasHeaderExtra, hasContent, setter);
     }
 
     public AnimatorListenerAdapter getProgressAnimatorListener() {
@@ -190,10 +202,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 +220,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/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index 7cd9651..9191048 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -17,6 +17,8 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
+import android.util.SparseArray;
+import android.view.animation.Interpolator;
 
 import com.android.launcher3.LauncherAnimUtils;
 
@@ -27,7 +29,12 @@
  */
 public class AnimatorSetBuilder {
 
+    public static final int ANIM_VERTICAL_PROGRESS = 0;
+    public static final int ANIM_OVERVIEW_TRANSLATION = 1;
+
     protected final ArrayList<Animator> mAnims = new ArrayList<>();
+
+    private final SparseArray<Interpolator> mInterpolators = new SparseArray<>();
     private long mStartDelay = 0;
 
     /**
@@ -49,4 +56,12 @@
         anim.setStartDelay(mStartDelay);
         return anim;
     }
+
+    public Interpolator getInterpolator(int animId, Interpolator fallback) {
+        return mInterpolators.get(animId, fallback);
+    }
+
+    public void setInterpolator(int animId, Interpolator interpolator) {
+        mInterpolators.put(animId, interpolator);
+    }
 }
diff --git a/src/com/android/launcher3/anim/PropertySetter.java b/src/com/android/launcher3/anim/PropertySetter.java
new file mode 100644
index 0000000..1f11f7e
--- /dev/null
+++ b/src/com/android/launcher3/anim/PropertySetter.java
@@ -0,0 +1,95 @@
+/*
+ * 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) {
+        if (view != null) {
+            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 == null || 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/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 818cea7..5c6946c 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -583,6 +583,12 @@
         }
 
         mDragObject.dragComplete = true;
+        if (mIsInPreDrag) {
+            if (dropTarget != null) {
+                dropTarget.onDragExit(mDragObject);
+            }
+            return;
+        }
 
         // Drop onto the target.
         boolean accepted = false;
@@ -591,17 +597,15 @@
             if (dropTarget.acceptDrop(mDragObject)) {
                 if (flingAnimation != null) {
                     flingAnimation.run();
-                } else if (!mIsInPreDrag) {
+                } else {
                     dropTarget.onDrop(mDragObject, mOptions);
                 }
                 accepted = true;
             }
         }
         final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null;
-        if (!mIsInPreDrag) {
-            mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
-            dispatchDropComplete(dropTargetAsView, accepted);
-        }
+        mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
+        dispatchDropComplete(dropTargetAsView, accepted);
     }
 
     private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
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/keyboard/FocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
index c07ab08..c50189c 100644
--- a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
@@ -236,4 +236,19 @@
             }
         }
     }
+
+    /**
+     * Simple subclass which assumes that the target view is a child of the container.
+     */
+    public static class SimpleFocusIndicatorHelper extends FocusIndicatorHelper {
+
+        public SimpleFocusIndicatorHelper(View container) {
+            super(container);
+        }
+
+        @Override
+        public void viewToRect(View v, Rect outRect) {
+            outRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
+        }
+    }
 }
diff --git a/src/com/android/launcher3/keyboard/FocusedItemDecorator.java b/src/com/android/launcher3/keyboard/FocusedItemDecorator.java
index 9c80b0f..05ae406 100644
--- a/src/com/android/launcher3/keyboard/FocusedItemDecorator.java
+++ b/src/com/android/launcher3/keyboard/FocusedItemDecorator.java
@@ -17,13 +17,14 @@
 package com.android.launcher3.keyboard;
 
 import android.graphics.Canvas;
-import android.graphics.Rect;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.RecyclerView.ItemDecoration;
 import android.support.v7.widget.RecyclerView.State;
 import android.view.View;
 import android.view.View.OnFocusChangeListener;
 
+import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
+
 /**
  * {@link ItemDecoration} for drawing and animating focused view background.
  */
@@ -32,13 +33,7 @@
     private FocusIndicatorHelper mHelper;
 
     public FocusedItemDecorator(View container) {
-        mHelper = new FocusIndicatorHelper(container) {
-
-            @Override
-            public void viewToRect(View v, Rect outRect) {
-                outRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
-            }
-        };
+        mHelper = new SimpleFocusIndicatorHelper(container);
     }
 
     public OnFocusChangeListener getFocusListener() {
diff --git a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
index 4fc7d8a..94ae39b 100644
--- a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
@@ -155,8 +155,8 @@
         int lineWidth = (int) (availableWidth / mNumPagesFloat);
         int lineLeft = (int) (progress * (availableWidth - lineWidth));
         int lineRight = lineLeft + lineWidth;
-        canvas.drawRect(lineLeft, canvas.getHeight() - mLineHeight, lineRight, canvas.getHeight(),
-                mLinePaint);
+        canvas.drawRoundRect(lineLeft, canvas.getHeight() / 2 - mLineHeight / 2, lineRight,
+                canvas.getHeight() / 2 + mLineHeight / 2, mLineHeight, mLineHeight, mLinePaint);
     }
 
     @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/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/shortcuts/DeepShortcutTextView.java b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
index 1a5297d..c809f27 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
@@ -22,6 +22,7 @@
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.widget.Toast;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
@@ -33,7 +34,9 @@
 public class DeepShortcutTextView extends BubbleTextView {
     private final Rect mDragHandleBounds = new Rect();
     private final int mDragHandleWidth;
-    private boolean mShouldPerformClick = true;
+    private boolean mShowInstructionToast = false;
+
+    private Toast mInstructionToast;
 
     public DeepShortcutTextView(Context context) {
         this(context, null, 0);
@@ -70,14 +73,29 @@
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            // Ignore clicks on the drag handle (long clicks still start the drag).
-            mShouldPerformClick = !mDragHandleBounds.contains((int) ev.getX(), (int) ev.getY());
+            // Show toast if user touches the drag handle (long clicks still start the drag).
+            mShowInstructionToast = mDragHandleBounds.contains((int) ev.getX(), (int) ev.getY());
         }
         return super.onTouchEvent(ev);
     }
 
     @Override
     public boolean performClick() {
-        return mShouldPerformClick && super.performClick();
+        if (mShowInstructionToast) {
+            showToast();
+            return true;
+        }
+        return super.performClick();
+    }
+
+    private void showToast() {
+        if (mInstructionToast != null) {
+            mInstructionToast.cancel();
+        }
+        CharSequence msg = Utilities.wrapForTts(
+                getContext().getText(R.string.long_press_shortcut_to_add),
+                getContext().getString(R.string.long_accessible_way_to_add_shortcut));
+        mInstructionToast = Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT);
+        mInstructionToast.show();
     }
 }
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 89a9e2d..90d3821 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -35,7 +35,7 @@
 
     private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE |
             FLAG_DISABLE_ACCESSIBILITY | FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED |
-            FLAG_DISABLE_PAGE_CLIPPING | FLAG_PAGE_BACKGROUNDS;
+            FLAG_DISABLE_PAGE_CLIPPING | FLAG_PAGE_BACKGROUNDS | FLAG_HIDE_BACK_BUTTON;
 
     public SpringLoadedState(int id) {
         super(id, ContainerType.OVERVIEW, SPRING_LOADED_TRANSITION_MS, STATE_FLAGS);
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
new file mode 100644
index 0000000..a22f450
--- /dev/null
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -0,0 +1,241 @@
+/*
+ * 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.TimeInterpolator;
+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;
+        updateProgress(deltaProgress + mStartProgress);
+        return true;
+    }
+
+    protected void updateProgress(float fraction) {
+        mCurrentAnimation.setPlayFraction(fraction);
+    }
+
+    @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 * mProgressMultiplier, 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 * mProgressMultiplier, 0f, 1f);
+                duration = SwipeDetector.calculateDuration(velocity,
+                        Math.min(progress, 1) - endProgress);
+            }
+        }
+
+        mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState, logAction));
+        ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
+        anim.setFloatValues(startProgress, endProgress);
+        updateSwipeCompleteAnimation(anim, duration, targetState, velocity, fling);
+        anim.start();
+    }
+
+    protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
+            LauncherState targetState, float velocity, boolean isFling) {
+        animator.setDuration(expectedDuration)
+                .setInterpolator(scrollInterpolatorForVelocity(velocity));
+    }
+
+    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..2f9cf3a
--- /dev/null
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -0,0 +1,150 @@
+/*
+ * 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;
+        }
+
+        final boolean result;
+        if (mLongPressState == STATE_COMPLETED) {
+            // We have handled the touch, so workspace does not need to know anything anymore.
+            result = true;
+        } else if (mLongPressState == STATE_REQUESTED) {
+            mWorkspace.onTouchEvent(ev);
+            if (mWorkspace.isHandlingTouch()) {
+                cancelLongPress();
+            }
+
+            result = true;
+        } else {
+            // We don't want to handle touch, let workspace handle it as usual.
+            result = false;
+        }
+        if (action == ACTION_UP || action == ACTION_CANCEL) {
+            cancelLongPress();
+        }
+        return result;
+    }
+
+    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/NoLocaleSQLiteHelper.java b/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java
new file mode 100644
index 0000000..05a7d27
--- /dev/null
+++ b/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java
@@ -0,0 +1,56 @@
+/*
+ * 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.util;
+
+import static android.database.sqlite.SQLiteDatabase.NO_LOCALIZED_COLLATORS;
+
+import static com.android.launcher3.Utilities.ATLEAST_P;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.database.DatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.database.sqlite.SQLiteDatabase.OpenParams;
+import android.database.sqlite.SQLiteOpenHelper;
+
+/**
+ * Extension of {@link SQLiteOpenHelper} which avoids creating default locale table by
+ * A context wrapper which creates databases without support for localized collators.
+ */
+public abstract class NoLocaleSQLiteHelper extends SQLiteOpenHelper {
+
+    public NoLocaleSQLiteHelper(Context context, String name, int version) {
+        super(ATLEAST_P ? context : new NoLocalContext(context), name, null, version);
+        if (ATLEAST_P) {
+            setOpenParams(new OpenParams.Builder().addOpenFlags(NO_LOCALIZED_COLLATORS).build());
+        }
+    }
+
+    private static class NoLocalContext extends ContextWrapper {
+        public NoLocalContext(Context base) {
+            super(base);
+        }
+
+        @Override
+        public SQLiteDatabase openOrCreateDatabase(
+                String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) {
+            return super.openOrCreateDatabase(
+                    name, mode | Context.MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/NoLocaleSqliteContext.java b/src/com/android/launcher3/util/NoLocaleSqliteContext.java
deleted file mode 100644
index c8a5ffb..0000000
--- a/src/com/android/launcher3/util/NoLocaleSqliteContext.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.android.launcher3.util;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.database.DatabaseErrorHandler;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteDatabase.CursorFactory;
-
-/**
- * A context wrapper which creates databases without support for localized collators.
- */
-public class NoLocaleSqliteContext extends ContextWrapper {
-
-    public NoLocaleSqliteContext(Context context) {
-        super(context);
-    }
-
-    @Override
-    public SQLiteDatabase openOrCreateDatabase(
-            String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) {
-        return super.openOrCreateDatabase(
-                name, mode | Context.MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler);
-    }
-}
diff --git a/src/com/android/launcher3/util/SQLiteCacheHelper.java b/src/com/android/launcher3/util/SQLiteCacheHelper.java
index 9084bfb..44c1762 100644
--- a/src/com/android/launcher3/util/SQLiteCacheHelper.java
+++ b/src/com/android/launcher3/util/SQLiteCacheHelper.java
@@ -92,10 +92,10 @@
     /**
      * A private inner class to prevent direct DB access.
      */
-    private class MySQLiteOpenHelper extends SQLiteOpenHelper {
+    private class MySQLiteOpenHelper extends NoLocaleSQLiteHelper {
 
         public MySQLiteOpenHelper(Context context, String name, int version) {
-            super(new NoLocaleSqliteContext(context), name, null, version);
+            super(context, name, version);
         }
 
         @Override
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..49a9dc7 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
@@ -25,7 +25,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
@@ -33,8 +32,9 @@
  */
 public class AllAppsState extends LauncherState {
 
-    private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY
-            | FLAG_SHOW_SCRIM | FLAG_ALL_APPS_SCRIM;
+    private static final float PARALLAX_COEFFICIENT = .125f;
+
+    private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY | FLAG_ALL_APPS_SCRIM;
 
     private static final PageAlphaProvider PAGE_ALPHA_PROVIDER = new PageAlphaProvider(DEACCEL_2) {
         @Override
@@ -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
@@ -75,8 +75,7 @@
     @Override
     public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
         return new float[] { 1f, 0,
-                -launcher.getAllAppsController().getShiftRange()
-                        * AllAppsTransitionController.PARALLAX_COEFFICIENT};
+                -launcher.getAllAppsController().getShiftRange() * PARALLAX_COEFFICIENT};
     }
 
     @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..94abcce 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -16,11 +16,10 @@
 
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-import android.graphics.PointF;
+import android.view.View;
 import android.view.View.AccessibilityDelegate;
 
+import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.util.TouchController;
@@ -29,7 +28,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 +37,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) { }
@@ -53,4 +47,8 @@
     public static void onStart(Launcher launcher) { }
 
     public static void onTrimMemory(Launcher launcher, int level) { }
+
+    public static View[] getHotseatExtraContent(Hotseat hotseat) {
+        return new View[0];
+    }
 }