Merge "Removing horizontal bar from page indicator" into ub-launcher3-master
diff --git a/quickstep/res/drawable/task_thumbnail_background.xml b/quickstep/res/drawable/task_thumbnail_background.xml
index 603380e..f1f48ac 100644
--- a/quickstep/res/drawable/task_thumbnail_background.xml
+++ b/quickstep/res/drawable/task_thumbnail_background.xml
@@ -14,6 +14,5 @@
      limitations under the License.
 -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
-    <solid android:color="#FF000000" />
     <corners android:radius="2dp" />
 </shape>
diff --git a/src/com/android/launcher3/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
similarity index 80%
rename from src/com/android/launcher3/states/AllAppsState.java
rename to quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index ed3023a..1064492 100644
--- a/src/com/android/launcher3/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -13,9 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.states;
+package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
+import static com.android.launcher3.allapps.DiscoveryBounce.APPS_VIEW_SHOWN;
 
 import android.view.View;
 
@@ -30,8 +31,6 @@
  */
 public class AllAppsState extends LauncherState {
 
-    public static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown";
-
     private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY;
 
     public AllAppsState(int id) {
@@ -57,4 +56,15 @@
     public View getFinalFocus(Launcher launcher) {
         return launcher.getAppsView();
     }
+
+    @Override
+    public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
+        // TODO: interpolate
+        return LauncherState.OVERVIEW.getWorkspaceScaleAndTranslation(launcher);
+    }
+
+    @Override
+    public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
+        return (i) -> 0;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DragPauseDetector.java b/quickstep/src/com/android/launcher3/uioverrides/DragPauseDetector.java
new file mode 100644
index 0000000..1977e93
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/DragPauseDetector.java
@@ -0,0 +1,80 @@
+/*
+ * 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 mEnabled = true;
+    private boolean mTriggered = false;
+
+    public DragPauseDetector(Runnable onPauseCallback) {
+        mOnPauseCallback = onPauseCallback;
+
+        mAlarm = new Alarm();
+        mAlarm.setOnAlarmListener(this);
+        mAlarm.setAlarm(PAUSE_DURATION);
+    }
+
+    public void onDrag(float displacement, float velocity) {
+        if (mTriggered || !mEnabled) {
+            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 && mEnabled) {
+            mTriggered = true;
+            mOnPauseCallback.run();
+        }
+    }
+
+    public boolean isTriggered () {
+        return mTriggered;
+    }
+
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    public void setEnabled(boolean isEnabled) {
+        if (mEnabled != isEnabled) {
+            mEnabled = isEnabled;
+            if (isEnabled && !mTriggered) {
+                mAlarm.setAlarm(PAUSE_DURATION);
+            } else if (!isEnabled) {
+                mAlarm.cancelAlarm();
+            }
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index a70ee70..3458a3f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -17,10 +17,12 @@
 
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 
+import android.graphics.Rect;
 import android.view.View;
 
 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;
 
@@ -29,7 +31,7 @@
  */
 public class OverviewState extends LauncherState {
 
-    private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE
+    private static final int STATE_FLAGS = FLAG_SHOW_SCRIM
             | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED;
 
     public OverviewState(int id) {
@@ -38,8 +40,20 @@
 
     @Override
     public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
-        // TODO: Find a better transition
-        return new float[] {0f, 0};
+        Rect pageRect = new Rect();
+        RecentsView.getPageRect(launcher, pageRect);
+        Workspace ws = launcher.getWorkspace();
+        float childWidth = ws.getNormalChildWidth();
+        if (childWidth <= 0 || pageRect.isEmpty()) {
+            return super.getWorkspaceScaleAndTranslation(launcher);
+        }
+
+        Rect insets = launcher.getDragLayer().getInsets();
+        float scale = pageRect.width() / childWidth;
+
+        float halfHeight = ws.getHeight() / 2;
+        float childTop = halfHeight - scale * (halfHeight - ws.getPaddingTop() - insets.top);
+        return new float[] {scale, pageRect.top - childTop};
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index da1eff9..60cd0c2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,39 +15,90 @@
  */
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.WorkspaceStateTransitionAnimation.NO_ANIM_PROPERTY_SETTER;
-
-import android.animation.AnimatorSet;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.view.View;
 
 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.WorkspaceStateTransitionAnimation.AnimatedPropertySetter;
-import com.android.launcher3.WorkspaceStateTransitionAnimation.PropertySetter;
-import com.android.launcher3.anim.AnimationLayerSet;
+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;
 
 public class RecentsViewStateController implements StateHandler {
 
     private final Launcher mLauncher;
+    private final RecentsView mRecentsView;
+
+    private final AnimatedFloat mTransitionProgress = new AnimatedFloat(this::applyProgress);
+    // 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::applyProgress);
 
     public RecentsViewStateController(Launcher launcher) {
         mLauncher = launcher;
+        mRecentsView = launcher.getOverviewPanel();
+        mRecentsView.setStateController(this);
     }
 
     @Override
     public void setState(LauncherState state) {
-        setState(state, NO_ANIM_PROPERTY_SETTER);
+        setVisibility(state == LauncherState.OVERVIEW);
+        setTransitionProgress(state == LauncherState.OVERVIEW ? 1 : 0);
     }
 
     @Override
-    public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
-            AnimatorSet anim, AnimationConfig config) {
-        setState(toState, new AnimatedPropertySetter(config.duration, layerViews, anim));
+    public void setStateWithAnimation(LauncherState toState,
+            AnimatorSetBuilder builder, AnimationConfig config) {
+        ObjectAnimator progressAnim =
+                mTransitionProgress.animateToValue(toState == LauncherState.OVERVIEW ? 1 : 0);
+        progressAnim.setDuration(config.duration);
+        progressAnim.setInterpolator(Interpolators.LINEAR);
+        builder.play(progressAnim);
+
+        ObjectAnimator visibilityAnim = animateVisibility(toState == LauncherState.OVERVIEW);
+        visibilityAnim.setDuration(config.duration);
+        visibilityAnim.setInterpolator(Interpolators.LINEAR);
+        builder.play(visibilityAnim);
     }
 
-    private void setState(LauncherState state, PropertySetter setter) {
-        setter.setViewAlpha(null, mLauncher.getOverviewPanel(),
-                state == LauncherState.OVERVIEW ? 1 : 0);
+    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);
+                }
+            });
+        } else {
+            anim.addListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    mRecentsView.setVisibility(View.GONE);
+                }
+            });
+        }
+        return anim;
+    }
+
+    public void setTransitionProgress(float progress) {
+        mTransitionProgress.cancelAnimation();
+        mTransitionProgress.updateValue(progress);
+    }
+
+    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
new file mode 100644
index 0000000..651a753
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaggedAnimatorSetBuilder.java
@@ -0,0 +1,51 @@
+/*
+ * 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/TwoStepSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
new file mode 100644
index 0000000..299db47
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
@@ -0,0 +1,490 @@
+/*
+ * 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.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.support.animation.SpringAnimation;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.animation.Interpolator;
+
+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.allapps.AllAppsContainerView;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.SpringAnimationHandler;
+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.TouchController;
+
+import java.util.ArrayList;
+
+/**
+ * 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;
+
+    private final Launcher mLauncher;
+    private final SwipeDetector mDetector;
+
+    private boolean mNoIntercept;
+    private int mStartContainerType;
+
+    private DragPauseDetector mDragPauseDetector;
+    private TaggedAnimatorSetBuilder mTaggedAnimatorSetBuilder;
+    private AnimatorSet mQuickOverviewAnimation;
+    private boolean mAnimatingToOverview;
+    private TwoStateAnimationController mTwoStateAnimationController;
+
+    private AnimatorPlaybackController mCurrentAnimation;
+    private LauncherState mToState;
+
+    private float mStartProgress;
+    // Ratio of transition process [0, 1] to drag displacement (px)
+    private float mProgressMultiplier;
+
+    private SpringAnimationHandler[] mSpringHandlers;
+
+    public TwoStepSwipeController(Launcher l) {
+        mLauncher = l;
+        mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
+    }
+
+    private boolean canInterceptTouch(MotionEvent ev) {
+        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 (mAnimatingToOverview) {
+            return false;
+        }
+        if (mCurrentAnimation != null) {
+            // If we are already animating from a previous state, we can intercept.
+            return true;
+        }
+        if (mLauncher.isInState(ALL_APPS) && !mLauncher.getAppsView().shouldContainerScroll(ev)) {
+            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();
+        }
+    }
+
+    private void initSprings() {
+        AllAppsContainerView appsView = mLauncher.getAppsView();
+
+        SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
+        if (handler == null) {
+            mSpringHandlers = new SpringAnimationHandler[0];
+            return;
+        }
+
+        ArrayList<SpringAnimationHandler> handlers = new ArrayList<>();
+        handlers.add(handler);
+
+        SpringAnimation searchSpring = appsView.getSearchUiManager().getSpringForFling();
+        if (searchSpring != null) {
+            SpringAnimationHandler searchHandler =
+                    new SpringAnimationHandler(Y_DIRECTION, handler.getFactory());
+            searchHandler.add(searchSpring, true /* setDefaultValues */);
+            handlers.add(searchHandler);
+        }
+
+        mSpringHandlers = handlers.toArray(new SpringAnimationHandler[handlers.size()]);
+    }
+
+    @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 (mSpringHandlers == null) {
+                initSprings();
+            }
+        }
+
+        if (mNoIntercept) {
+            return false;
+        }
+
+        onControllerTouchEvent(ev);
+        return mDetector.isDraggingOrSettling();
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        for (SpringAnimationHandler h : mSpringHandlers) {
+            h.addMovement(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);
+            mTaggedAnimatorSetBuilder = new TaggedAnimatorSetBuilder();
+
+            // Build current animation
+            mToState = mLauncher.isInState(ALL_APPS) ? NORMAL : ALL_APPS;
+            mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(
+                    mToState, mTaggedAnimatorSetBuilder, maxAccuracy);
+
+            mCurrentAnimation.getTarget().addListener(this);
+            mStartProgress = 0;
+            mProgressMultiplier = (mLauncher.isInState(ALL_APPS) ? 1 : -1) / range;
+            mCurrentAnimation.dispatchOnStart();
+        } else {
+            mCurrentAnimation.pause();
+            mStartProgress = mCurrentAnimation.getProgressFraction();
+        }
+
+        for (SpringAnimationHandler h : mSpringHandlers) {
+            h.skipToEnd();
+        }
+    }
+
+    private float getShiftRange() {
+        return mLauncher.getAllAppsController().getShiftRange();
+    }
+
+    @Override
+    public boolean onDrag(float displacement, float velocity) {
+        mDragPauseDetector.onDrag(displacement, velocity);
+
+        float deltaProgress = mProgressMultiplier * displacement;
+        mCurrentAnimation.setPlayFraction(deltaProgress + mStartProgress);
+        return true;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        if (!fling && mDragPauseDetector.isEnabled() && mDragPauseDetector.isTriggered()) {
+            snapToOverview(velocity);
+            return;
+        }
+
+        final long animationDuration;
+        final int logAction;
+        final LauncherState targetState;
+        final float progress = mCurrentAnimation.getProgressFraction();
+
+        if (fling) {
+            logAction = Touch.FLING;
+            if (velocity < 0) {
+                targetState = ALL_APPS;
+                animationDuration = SwipeDetector.calculateDuration(velocity,
+                        mToState == ALL_APPS ? (1 - progress) : progress);
+            } else {
+                targetState = NORMAL;
+                animationDuration = SwipeDetector.calculateDuration(velocity,
+                        mToState == ALL_APPS ? progress : (1 - progress));
+            }
+            // snap to top or bottom using the release velocity
+        } else {
+            logAction = Touch.SWIPE;
+            if (progress > SUCCESS_TRANSITION_PROGRESS) {
+                targetState = mToState;
+                animationDuration = SwipeDetector.calculateDuration(velocity, 1 - progress);
+            } else {
+                targetState = mToState == ALL_APPS ? NORMAL : ALL_APPS;
+                animationDuration = SwipeDetector.calculateDuration(velocity, progress);
+            }
+        }
+
+        if (fling && targetState == ALL_APPS) {
+            for (SpringAnimationHandler h : mSpringHandlers) {
+                // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
+                h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
+            }
+        }
+        mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState, logAction));
+
+        float nextFrameProgress = Utilities.boundToRange(
+                progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
+
+        ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
+        anim.setFloatValues(nextFrameProgress, targetState == mToState ? 1f : 0f);
+        anim.setDuration(animationDuration);
+        anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
+        anim.start();
+
+        // TODO: Re-enable later
+        mDragPauseDetector.setEnabled(false);
+    }
+
+    private void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
+        if (targetState == mToState) {
+            // Transition complete. log the action
+            mLauncher.getUserEventDispatcher().logActionOnContainer(logAction,
+                    mToState == ALL_APPS ? Direction.UP : Direction.DOWN,
+                    mStartContainerType, 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 snapToOverview(float velocity) {
+        mAnimatingToOverview = true;
+
+        final float progress = mCurrentAnimation.getProgressFraction();
+        float endProgress = mToState == NORMAL ? 1f : 0f;
+        long animationDuration = SwipeDetector.calculateDuration(
+                velocity, Math.abs(endProgress - progress));
+        float nextFrameProgress = Utilities.boundToRange(
+                progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
+
+        mCurrentAnimation.setEndAction(() -> {
+            // TODO: Add logging
+            clearState();
+            mLauncher.getStateManager().goToState(OVERVIEW, false /* animated */);
+        });
+
+        if (mTwoStateAnimationController != null) {
+            mTwoStateAnimationController.goBackToStart(endProgress);
+        }
+
+        ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
+        anim.setFloatValues(nextFrameProgress, endProgress);
+        anim.setDuration(animationDuration);
+        anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
+        anim.start();
+    }
+
+    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 twoStepAnimator) {
+        if (mAnimatingToOverview) {
+            return;
+        }
+
+        // The remaining state handlers are on the OVERVIEW state. Create two animations, one
+        // towards the NORMAL state and one towards ALL_APPS state and control them based on the
+        // swipe progress.
+        AnimationConfig config = new AnimationConfig();
+        config.duration = (long) (2 * getShiftRange());
+        config.userControlled = true;
+
+        LauncherState fromState = mToState == ALL_APPS ? NORMAL : ALL_APPS;
+        AnimatorSetBuilder builderToTargetState = new AnimatorSetBuilder();
+        AnimatorSetBuilder builderToSourceState = new AnimatorSetBuilder();
+
+        StateHandler[] handlers = mLauncher.getStateManager().getStateHandlers();
+        for (int i = OTHER_HANDLERS_START_INDEX; i < handlers.length; i++) {
+            handlers[i].setStateWithAnimation(mToState, builderToTargetState, config);
+            handlers[i].setStateWithAnimation(fromState, builderToSourceState, config);
+        }
+
+        mTwoStateAnimationController = new TwoStateAnimationController(
+                AnimatorPlaybackController.wrap(builderToSourceState.build(), config.duration),
+                AnimatorPlaybackController.wrap(builderToTargetState.build(), config.duration),
+                twoStepAnimator.getAnimatedFraction());
+        twoStepAnimator.addUpdateListener(mTwoStateAnimationController);
+    }
+
+    private void clearState() {
+        mCurrentAnimation = null;
+        mTaggedAnimatorSetBuilder = null;
+        if (mDragPauseDetector != null) {
+            mDragPauseDetector.setEnabled(false);
+        }
+        mDragPauseDetector = null;
+
+        if (mQuickOverviewAnimation != null) {
+            mQuickOverviewAnimation.cancel();
+            mQuickOverviewAnimation = null;
+        }
+        mTwoStateAnimationController = null;
+        mAnimatingToOverview = false;
+
+        mDetector.finishedScrolling();
+    }
+
+    /**
+     * {@link AnimatorUpdateListener} which interpolates two animations based the progress
+     */
+    private static class TwoStateAnimationController implements AnimatorUpdateListener {
+
+        private final AnimatorPlaybackController mControllerTowardsStart;
+        private final AnimatorPlaybackController mControllerTowardsEnd;
+
+        private Interpolator mInterpolator = Interpolators.LINEAR;
+        private float mStartFraction;
+        private float mLastFraction;
+
+        TwoStateAnimationController(AnimatorPlaybackController controllerTowardsStart,
+                AnimatorPlaybackController controllerTowardsEnd, float startFraction) {
+            mControllerTowardsStart = controllerTowardsStart;
+            mControllerTowardsEnd = controllerTowardsEnd;
+            mLastFraction = mStartFraction = startFraction;
+        }
+
+        @Override
+        public void onAnimationUpdate(ValueAnimator valueAnimator) {
+            mLastFraction = mInterpolator.getInterpolation(valueAnimator.getAnimatedFraction());
+            if (mLastFraction > mStartFraction) {
+                if (mStartFraction >= 1) {
+                    mControllerTowardsEnd.setPlayFraction(0);
+                } else {
+                    mControllerTowardsEnd.setPlayFraction(
+                            (mLastFraction - mStartFraction) / (1 - mStartFraction));
+                }
+            } else {
+                if (mStartFraction <= 0) {
+                    mControllerTowardsStart.setPlayFraction(0);
+                } else {
+                    mControllerTowardsStart.setPlayFraction(
+                            (mStartFraction - mLastFraction) / mStartFraction);
+                }
+            }
+        }
+
+        /**
+         * Changes the interpolator such that from this point ({@link #mLastFraction}), the
+         * animation run towards {@link #mStartFraction}. This allows us to animate the UI back
+         * to the original point.
+         * @param endFraction expected end point for this animation. Should either be 0 or 1.
+         */
+        public void goBackToStart(float endFraction) {
+            if (mLastFraction == mStartFraction || mLastFraction == endFraction) {
+                mInterpolator = (v) -> mStartFraction;
+            } else if (mLastFraction > mStartFraction && endFraction < mStartFraction) {
+                mInterpolator = (v) -> Math.max(v, mStartFraction);
+            } else if (mLastFraction < mStartFraction && endFraction > mStartFraction) {
+                mInterpolator = (v) -> Math.min(mStartFraction, v);
+            } else {
+                final float start = mLastFraction;
+                final float range = endFraction - mLastFraction;
+                mInterpolator = (v) ->
+                        SwipeDetector.interpolate(start, mStartFraction, (v - start) / range);
+            }
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index a48a65c..5e280b6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -24,14 +24,13 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.R;
-import com.android.launcher3.VerticalSwipeController;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.widget.WidgetsFullSheet;
 
 public class UiFactory {
 
     public static TouchController[] createTouchControllers(Launcher launcher) {
-        return new TouchController[] {new VerticalSwipeController(launcher)};
+        return new TouchController[] {new TwoStepSwipeController(launcher)};
     }
 
     public static AccessibilityDelegate newPageIndicatorAccessibilityDelegate() {
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
index 1f6781e..214b3f3 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -47,9 +47,7 @@
     }
 
     public ObjectAnimator animateToValue(float v) {
-        if (mValueAnimator != null) {
-            mValueAnimator.cancel();
-        }
+        cancelAnimation();
         mValueAnimator = ObjectAnimator.ofFloat(this, VALUE, v);
         mValueAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -73,6 +71,12 @@
         }
     }
 
+    public void cancelAnimation() {
+        if (mValueAnimator != null) {
+            mValueAnimator.cancel();
+        }
+    }
+
     public ObjectAnimator getCurrentAnimation() {
         return mValueAnimator;
     }
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
index 4c3a9ad..75db45b 100644
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -42,6 +42,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.states.InternalStateHandler;
+import com.android.launcher3.uioverrides.RecentsViewStateController;
 import com.android.launcher3.util.TraceHelper;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.shared.recents.model.Task;
@@ -94,6 +95,7 @@
     private Launcher mLauncher;
     private SnapshotDragView mDragView;
     private RecentsView mRecentsView;
+    private RecentsViewStateController mStateController;
     private Hotseat mHotseat;
     private RecentsTaskLoadPlan mLoadPlan;
 
@@ -178,11 +180,13 @@
         mDragView.setPivotX(0);
         mDragView.setPivotY(0);
         mRecentsView = mLauncher.getOverviewPanel();
+        mStateController = mRecentsView.getStateController();
         mHotseat = mLauncher.getHotseat();
 
         // Optimization
         mLauncher.getAppsView().setVisibility(View.GONE);
-        mRecentsView.setVisibility(View.GONE);
+        mStateController.setTransitionProgress(1);
+        mStateController.setVisibility(false);
         TraceHelper.partitionSection("TouchInt", "Launcher on new intent");
     }
 
@@ -209,17 +213,9 @@
         }
 
         if (mTargetRect.isEmpty()) {
+            RecentsView.getPageRect(mLauncher, mTargetRect);
             DragLayer dl = mLauncher.getDragLayer();
             mSourceRect.set(0, 0, dl.getWidth(), dl.getHeight());
-            Rect targetPadding = RecentsView.getPadding(mLauncher);
-            Rect insets = dl.getInsets();
-            mTargetRect.set(
-                    targetPadding.left + insets.left,
-                    targetPadding.top + insets.top,
-                    mSourceRect.right - targetPadding.right - insets.right,
-                    mSourceRect.bottom - targetPadding.bottom - insets.bottom);
-            mTargetRect.top += mLauncher.getResources()
-                    .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
         }
 
         float shift = mCurrentShift.value * mActivityMultiplier.value;
@@ -246,12 +242,15 @@
 
     private void setTaskPlanToUi() {
         mRecentsView.update(mLoadPlan);
-        mRecentsView.setVisibility(View.VISIBLE);
-
-        // Animate alpha
-        mRecentsView.setAlpha(0);
-        mRecentsView.animate().alpha(1).setDuration(RECENTS_VIEW_VISIBILITY_DURATION)
-                .withEndAction(() -> mStateCallback.setState(STATE_RECENTS_FULLY_VISIBLE));
+        ObjectAnimator anim = mStateController.animateVisibility(true /* isVisible */)
+                .setDuration(RECENTS_VIEW_VISIBILITY_DURATION);
+        anim.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                mStateCallback.setState(STATE_RECENTS_FULLY_VISIBLE);
+            }
+        });
+        anim.start();
     }
 
     @UiThread
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
index 34ed7f1..a107343 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -16,6 +16,7 @@
 
 package com.android.quickstep;
 
+import android.animation.LayoutTransition;
 import android.animation.TimeInterpolator;
 import android.content.Context;
 import android.graphics.Rect;
@@ -25,8 +26,11 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.uioverrides.RecentsViewStateController;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.shared.recents.model.RecentsTaskLoader;
 import com.android.systemui.shared.recents.model.Task;
@@ -56,6 +60,7 @@
 
     private boolean mOverviewStateEnabled;
     private boolean mTaskStackListenerRegistered;
+    private LayoutTransition mLayoutTransition;
 
     private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
@@ -70,6 +75,8 @@
         }
     };
 
+    private RecentsViewStateController mStateController;
+
     public RecentsView(Context context) {
         this(context, null);
     }
@@ -83,6 +90,18 @@
         setWillNotDraw(false);
         setPageSpacing((int) getResources().getDimension(R.dimen.recents_page_spacing));
         enableFreeScroll(true);
+        setupLayoutTransition();
+    }
+
+    private void setupLayoutTransition() {
+        // We want to show layout transitions when pages are deleted, to close the gap.
+        mLayoutTransition = new LayoutTransition();
+        mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
+        mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+
+        mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
+        mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
+        setLayoutTransition(mLayoutTransition);
     }
 
     @Override
@@ -111,6 +130,14 @@
         updateTaskStackListenerState();
     }
 
+    public void setStateController(RecentsViewStateController stateController) {
+        mStateController = stateController;
+    }
+
+    public RecentsViewStateController getStateController() {
+        return mStateController;
+    }
+
     public void setOverviewStateEnabled(boolean enabled) {
         mOverviewStateEnabled = enabled;
         updateTaskStackListenerState();
@@ -129,6 +156,7 @@
         // necessary)
         final LayoutInflater inflater = LayoutInflater.from(getContext());
         final ArrayList<Task> tasks = stack.getTasks();
+        setLayoutTransition(null);
         for (int i = getChildCount(); i < tasks.size(); i++) {
             final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
             addView(taskView);
@@ -138,6 +166,7 @@
             removeView(taskView);
             loader.unloadTaskData(taskView.getTask());
         }
+        setLayoutTransition(mLayoutTransition);
 
         // Rebind all task views
         for (int i = tasks.size() - 1; i >= 0; i--) {
@@ -171,7 +200,7 @@
         }
     }
 
-    public static Rect getPadding(Launcher launcher) {
+    private static Rect getPadding(Launcher launcher) {
         DeviceProfile profile = launcher.getDeviceProfile();
         Rect stableInsets = new Rect();
         WindowManagerWrapper.getInstance().getStableInsets(stableInsets);
@@ -185,6 +214,19 @@
         return padding;
     }
 
+    public static void getPageRect(Launcher launcher, Rect outRect) {
+        DragLayer dl = launcher.getDragLayer();
+        Rect targetPadding = getPadding(launcher);
+        Rect insets = dl.getInsets();
+        outRect.set(
+                targetPadding.left + insets.left,
+                targetPadding.top + insets.top,
+                dl.getWidth() - targetPadding.right - insets.right,
+                dl.getHeight() - targetPadding.bottom - insets.bottom);
+        outRect.top += launcher.getResources()
+                .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+    }
+
     @Override
     public void computeScroll() {
         super.computeScroll();
@@ -223,4 +265,12 @@
             }
         }
     }
+
+    public void onTaskDismissed(TaskView taskView) {
+        ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
+        removeView(taskView);
+        if (getChildCount() == 0) {
+            Launcher.getLauncher(getContext()).getStateManager().goToState(LauncherState.NORMAL);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
index ac9a778..a0ad618 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -16,15 +16,25 @@
 
 package com.android.quickstep;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.Property;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import com.android.launcher3.R;
-import com.android.launcher3.uioverrides.OverviewState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.touch.SwipeDetector;
 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;
@@ -39,11 +49,38 @@
 /**
  * A task in the Recents view.
  */
-public class TaskView extends FrameLayout implements TaskCallbacks {
+public class TaskView extends FrameLayout implements TaskCallbacks, SwipeDetector.Listener {
+
+    private static final int SWIPE_DIRECTIONS = SwipeDetector.DIRECTION_POSITIVE;
+
+    /**
+     * The task will appear fully dismissed when the distance swiped
+     * reaches this percentage of the card height.
+     */
+    private static final float SWIPE_DISTANCE_HEIGHT_PERCENTAGE = 0.38f;
+
+    private static final Property<TaskView, Float> PROPERTY_SWIPE_PROGRESS =
+            new Property<TaskView, Float>(Float.class, "swipe_progress") {
+
+                @Override
+                public Float get(TaskView taskView) {
+                    return taskView.mSwipeProgress;
+                }
+
+                @Override
+                public void set(TaskView taskView, Float progress) {
+                    taskView.setSwipeProgress(progress);
+                }
+            };
 
     private Task mTask;
     private TaskThumbnailView mSnapshotView;
     private ImageView mIconView;
+    private SwipeDetector mSwipeDetector;
+    private float mSwipeDistance;
+    private float mSwipeProgress;
+    private Interpolator mAlphaInterpolator;
+    private Interpolator mSwipeAnimInterpolator;
 
     public TaskView(Context context) {
         this(context, null);
@@ -58,6 +95,11 @@
         setOnClickListener((view) -> {
             launchTask(true /* animate */);
         });
+
+        mSwipeDetector = new SwipeDetector(getContext(), this, SwipeDetector.VERTICAL);
+        mSwipeDetector.setDetectableScrollConditions(SWIPE_DIRECTIONS, false);
+        mAlphaInterpolator = Interpolators.ACCEL_1_5;
+        mSwipeAnimInterpolator = Interpolators.SCROLL_CUBIC;
     }
 
     @Override
@@ -67,6 +109,15 @@
         mIconView = findViewById(R.id.icon);
     }
 
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        View p = (View) getParent();
+        mSwipeDistance = (getMeasuredHeight() - p.getPaddingTop() - p.getPaddingBottom())
+                * SWIPE_DISTANCE_HEIGHT_PERCENTAGE;
+    }
+
     /**
      * Updates this task view to the given {@param task}.
      */
@@ -134,4 +185,78 @@
     public void onTaskWindowingModeChanged() {
         // Do nothing
     }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        mSwipeDetector.onTouchEvent(ev);
+        return super.onInterceptTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        mSwipeDetector.onTouchEvent(event);
+        return mSwipeDetector.isDraggingOrSettling() || super.onTouchEvent(event);
+    }
+
+    // Swipe detector methods
+
+    @Override
+    public void onDragStart(boolean start) {
+        getParent().requestDisallowInterceptTouchEvent(true);
+    }
+
+    @Override
+    public boolean onDrag(float displacement, float velocity) {
+        setSwipeProgress(Utilities.boundToRange(displacement / mSwipeDistance,
+                allowsSwipeUp() ? -1 : 0, allowsSwipeDown() ? 1 : 0));
+        return true;
+    }
+
+    /**
+     * Indicates the page is being removed.
+     * @param progress Ranges from -1 (fading upwards) to 1 (fading downwards).
+     */
+    private void setSwipeProgress(float progress) {
+        mSwipeProgress = progress;
+        float translationY = mSwipeProgress * mSwipeDistance;
+        float alpha = 1f - mAlphaInterpolator.getInterpolation(Math.abs(mSwipeProgress));
+        // Only change children to avoid changing our properties while dragging.
+        mIconView.setTranslationY(translationY);
+        mSnapshotView.setTranslationY(translationY);
+        mIconView.setAlpha(alpha);
+        mSnapshotView.setAlpha(alpha);
+    }
+
+    private boolean allowsSwipeUp() {
+        return (SWIPE_DIRECTIONS & SwipeDetector.DIRECTION_POSITIVE) != 0;
+    }
+
+    private boolean allowsSwipeDown() {
+        return (SWIPE_DIRECTIONS & SwipeDetector.DIRECTION_NEGATIVE) != 0;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        boolean movingAwayFromCenter = velocity < 0 == mSwipeProgress < 0;
+        boolean flingAway = fling && movingAwayFromCenter
+                && (allowsSwipeUp() && velocity < 0 || allowsSwipeDown() && velocity > 0);
+        final boolean shouldRemove = flingAway || (!fling && Math.abs(mSwipeProgress) > 0.5f);
+        float fromProgress = mSwipeProgress;
+        float toProgress = !shouldRemove ? 0f : mSwipeProgress < 0 ? -1f : 1f;
+        ValueAnimator swipeAnimator = ObjectAnimator.ofFloat(this, PROPERTY_SWIPE_PROGRESS,
+                fromProgress, toProgress);
+        swipeAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (shouldRemove) {
+                    ((RecentsView) getParent()).onTaskDismissed(TaskView.this);
+                }
+                mSwipeDetector.finishedScrolling();
+            }
+        });
+        swipeAnimator.setDuration(SwipeDetector.calculateDuration(velocity,
+                Math.abs(toProgress - fromProgress)));
+        swipeAnimator.setInterpolator(mSwipeAnimInterpolator);
+        swipeAnimator.start();
+    }
 }
diff --git a/res/color/all_apps_work_tab_text.xml b/res/color/all_apps_work_tab_text.xml
new file mode 100644
index 0000000..7279bf8
--- /dev/null
+++ b/res/color/all_apps_work_tab_text.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/work_profile_color" android:state_selected="true"/>
+    <item android:color="?android:attr/textColorTertiary"/>
+</selector>
\ No newline at end of file
diff --git a/res/layout-land/all_apps_fast_scroller.xml b/res/layout-land/all_apps_fast_scroller.xml
index 6a68f84..16aa2af 100644
--- a/res/layout-land/all_apps_fast_scroller.xml
+++ b/res/layout-land/all_apps_fast_scroller.xml
@@ -21,7 +21,7 @@
         android:id="@+id/fast_scroller_popup"
         style="@style/FastScrollerPopup"
         android:layout_alignParentEnd="true"
-        android:layout_alignTop="@+id/apps_list_view"
+        android:layout_below="@+id/search_container_all_apps"
         android:layout_marginTop="-5dp"
         android:layout_marginEnd="-45dp" />
 
@@ -31,7 +31,7 @@
         android:layout_height="wrap_content"
         android:layout_alignParentBottom="true"
         android:layout_alignParentEnd="true"
-        android:layout_alignParentTop="@+id/apps_list_view"
+        android:layout_below="@+id/search_container_all_apps"
         android:layout_marginEnd="-88dp"
         android:layout_marginTop="14dp"
         launcher:canThumbDetach="true" />
diff --git a/res/layout-sw720dp/all_apps_fast_scroller.xml b/res/layout-sw720dp/all_apps_fast_scroller.xml
index 12c15cc..5537bc6 100644
--- a/res/layout-sw720dp/all_apps_fast_scroller.xml
+++ b/res/layout-sw720dp/all_apps_fast_scroller.xml
@@ -21,7 +21,7 @@
         android:id="@+id/fast_scroller_popup"
         style="@style/FastScrollerPopup"
         android:layout_alignParentEnd="true"
-        android:layout_alignTop="@+id/apps_list_view"
+        android:layout_below="@+id/search_container_all_apps"
         android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
 
     <com.android.launcher3.views.RecyclerViewFastScroller
@@ -30,7 +30,7 @@
         android:layout_height="wrap_content"
         android:layout_alignParentBottom="true"
         android:layout_alignParentEnd="true"
-        android:layout_alignTop="@+id/apps_list_view"
+        android:layout_below="@+id/search_container_all_apps"
         android:layout_marginEnd="@dimen/fastscroll_end_margin"
         launcher:canThumbDetach="true" />
 
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index c42c15c..3f6be2c 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -31,7 +31,7 @@
 
     <include layout="@layout/all_apps_fast_scroller" />
 
-    <RelativeLayout
+    <com.android.launcher3.allapps.FloatingHeaderView
         android:id="@+id/all_apps_header"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
@@ -39,41 +39,48 @@
         android:clipToPadding="false"
         android:layout_below="@id/search_container_all_apps" >
 
-        <com.android.launcher3.allapps.PredictionRowView
-            android:id="@+id/header_content"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
+        <include layout="@layout/predictions_view" android:id="@+id/header_content" />
 
         <include layout="@layout/all_apps_divider"
             android:id="@+id/divider"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
+            android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
             android:layout_alignBottom="@+id/tabs" />
 
-        <com.android.launcher3.views.SlidingTabStrip
+        <com.android.launcher3.allapps.PersonalWorkSlidingTabStrip
             android:id="@+id/tabs"
             android:layout_width="match_parent"
             android:layout_height="@dimen/all_apps_header_tab_height"
+            android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
+            android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
             android:layout_below="@id/header_content"
-            android:orientation="horizontal" >
+            android:orientation="horizontal">
             <Button
                 android:id="@+id/tab_personal"
                 android:layout_width="0dp"
                 android:layout_height="match_parent"
                 android:layout_weight="1"
+                android:background="?android:attr/selectableItemBackground"
+                android:fontFamily="sans-serif-medium"
                 android:text="@string/all_apps_personal_tab"
+                android:textAllCaps="true"
                 android:textColor="@color/all_apps_tab_text"
-                android:background="?android:attr/selectableItemBackground"/>
+                android:textSize="14sp"/>
             <Button
                 android:id="@+id/tab_work"
                 android:layout_width="0dp"
                 android:layout_height="match_parent"
                 android:layout_weight="1"
+                android:background="?android:attr/selectableItemBackground"
+                android:fontFamily="sans-serif-medium"
                 android:text="@string/all_apps_work_tab"
-                android:textColor="@color/all_apps_tab_text"
-                android:background="?android:attr/selectableItemBackground"/>
-        </com.android.launcher3.views.SlidingTabStrip>
-    </RelativeLayout>
+                android:textAllCaps="true"
+                android:textColor="@color/all_apps_work_tab_text"
+                android:textSize="14sp"/>
+        </com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
+    </com.android.launcher3.allapps.FloatingHeaderView>
 
     <!-- Note: we are reusing/repurposing a system attribute for search layout, because of a
      platform bug, which prevents using custom attributes in <include> tag -->
diff --git a/res/layout/all_apps_fast_scroller.xml b/res/layout/all_apps_fast_scroller.xml
index 12c15cc..5537bc6 100644
--- a/res/layout/all_apps_fast_scroller.xml
+++ b/res/layout/all_apps_fast_scroller.xml
@@ -21,7 +21,7 @@
         android:id="@+id/fast_scroller_popup"
         style="@style/FastScrollerPopup"
         android:layout_alignParentEnd="true"
-        android:layout_alignTop="@+id/apps_list_view"
+        android:layout_below="@+id/search_container_all_apps"
         android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
 
     <com.android.launcher3.views.RecyclerViewFastScroller
@@ -30,7 +30,7 @@
         android:layout_height="wrap_content"
         android:layout_alignParentBottom="true"
         android:layout_alignParentEnd="true"
-        android:layout_alignTop="@+id/apps_list_view"
+        android:layout_below="@+id/search_container_all_apps"
         android:layout_marginEnd="@dimen/fastscroll_end_margin"
         launcher:canThumbDetach="true" />
 
diff --git a/res/layout/predictions_view.xml b/res/layout/predictions_view.xml
new file mode 100644
index 0000000..280290c
--- /dev/null
+++ b/res/layout/predictions_view.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<com.android.launcher3.allapps.PredictionRowView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content" />
\ No newline at end of file
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 294425d..3e34e16 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -132,4 +132,8 @@
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Pessoal"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Trabalho"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de trabalho"</string>
+    <!-- no translation found for bottom_work_tab_user_education_title (5785851780786322825) -->
+    <skip />
+    <!-- no translation found for bottom_work_tab_user_education_body (5834430249581360068) -->
+    <skip />
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 60f249e..93c5114 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -96,6 +96,7 @@
     <dimen name="all_apps_prediction_row_divider_height">17dp</dimen>
     <dimen name="all_apps_work_profile_tab_footer_top_padding">16dp</dimen>
     <dimen name="all_apps_work_profile_tab_footer_bottom_padding">20dp</dimen>
+    <dimen name="all_apps_tabs_side_padding">12dp</dimen>
 
 <!-- Search bar in All Apps -->
     <dimen name="all_apps_header_max_elevation">3dp</dimen>
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 2c6629d..da464c0 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -59,6 +59,10 @@
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
             | TYPE_QUICKSTEP_PREVIEW | TYPE_ON_BOARD_POPUP;
 
+    // Type of popups which should be kept open during launcher rebind
+    public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
+            | TYPE_QUICKSTEP_PREVIEW | TYPE_ON_BOARD_POPUP;
+
     protected boolean mIsOpen;
 
     public AbstractFloatingView(Context context, AttributeSet attrs) {
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index b315980..76c7845 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -17,13 +17,11 @@
 package com.android.launcher3;
 
 import android.content.Context;
-import android.graphics.Canvas;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.TextView;
 
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
@@ -68,6 +66,7 @@
         ViewGroup parent = (ViewGroup) getParent().getParent();
         mScrollbar = parent.findViewById(R.id.fast_scroller);
         mScrollbar.setRecyclerView(this, parent.findViewById(R.id.fast_scroller_popup));
+        onUpdateScrollbar(0);
     }
 
     /**
@@ -90,8 +89,10 @@
      */
     private boolean handleTouchEvent(MotionEvent ev) {
         // Move to mScrollbar's coordinate system.
-        int left = getLeft() - mScrollbar.getLeft();
-        int top = getTop() - mScrollbar.getTop();
+        // We need to take parent into account (view pager's location)
+        ViewGroup parent = (ViewGroup) getParent();
+        int left = parent.getLeft() + getLeft() - mScrollbar.getLeft();
+        int top = parent.getTop() + getTop() - mScrollbar.getTop() - getScrollBarTop();
         ev.offsetLocation(left, top);
         try {
             return mScrollbar.handleTouchEvent(ev);
@@ -112,7 +113,7 @@
      * Returns the height of the fast scroll bar
      */
     public int getScrollbarTrackHeight() {
-        return getHeight() - getScrollBarTop() - getPaddingBottom();
+        return mScrollbar.getHeight() - getScrollBarTop() - getPaddingBottom();
     }
 
     /**
@@ -130,12 +131,6 @@
         return availableScrollBarHeight;
     }
 
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        onUpdateScrollbar(0);
-        super.dispatchDraw(canvas);
-    }
-
     /**
      * Updates the scrollbar thumb offset to match the visible scroll of the recycler view.  It does
      * this by mapping the available scroll area of the recycler view to the available space for the
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index 3eca5cd..106671f 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -17,7 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.AlphaUpdateListener.updateVisibility;
-import static com.android.launcher3.Utilities.isAccessibilityEnabled;
+import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 
 import android.animation.TimeInterpolator;
 import android.content.Context;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 9ee59cc..44660ec 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -42,13 +42,11 @@
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
 import android.app.AlertDialog;
-import android.app.SearchManager;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks2;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.DialogInterface;
@@ -70,8 +68,6 @@
 import android.os.StrictMode;
 import android.os.UserHandle;
 import android.support.annotation.Nullable;
-import android.text.Selection;
-import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
 import android.text.method.TextKeyListener;
 import android.util.Log;
@@ -89,7 +85,6 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
-import android.view.inputmethod.InputMethodManager;
 import android.widget.Toast;
 
 import com.android.launcher3.DropTarget.DragObject;
@@ -98,10 +93,10 @@
 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.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.LauncherAppsCompatVO;
-import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -119,7 +114,6 @@
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.states.AllAppsState;
 import com.android.launcher3.states.InternalStateHandler;
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -240,8 +234,6 @@
     // that results in widgets being inflated in the wrong orientation.
     private int mOrientation;
 
-    private SpannableStringBuilder mDefaultKeySsb = null;
-
     @Thunk boolean mWorkspaceLoading = true;
 
     private boolean mPaused = true;
@@ -256,14 +248,13 @@
     private ModelWriter mModelWriter;
     private IconCache mIconCache;
     private LauncherAccessibilityDelegate mAccessibilityDelegate;
-    private boolean mHasFocus = false;
 
     private ObjectAnimator mScrimAnimator;
     private boolean mShouldFadeInScrim;
 
     private PopupDataProvider mPopupDataProvider;
 
-    private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<>();
+    private int mSynchronouslyBoundPage = PagedView.INVALID_PAGE;
 
     // We only want to get the SharedPreferences once since it does an FS stat each time we get
     // it from the context.
@@ -397,8 +388,7 @@
         }
 
         // For handling default keys
-        mDefaultKeySsb = new SpannableStringBuilder();
-        Selection.setSelection(mDefaultKeySsb, 0);
+        setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
 
         // On large interfaces, or on devices that a user has specifically enabled screen rotation,
         // we want the screen to auto-rotate based on the current orientation
@@ -843,15 +833,11 @@
         // Refresh shortcuts if the permission changed.
         mModel.refreshShortcutsIfRequired();
 
-        if (shouldShowDiscoveryBounce()) {
-            mAllAppsController.showDiscoveryBounce();
-        }
+        DiscoveryBounce.showIfNeeded(this);
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onResume();
         }
 
-        clearTypedText();
-
         TraceHelper.endSection("ON_RESUME");
     }
 
@@ -919,72 +905,6 @@
         }
     }
 
-    @Override
-    public Object onRetainNonConfigurationInstance() {
-        // Flag the loader to stop early before switching
-        if (mModel.isCurrentCallbacks(this)) {
-            mModel.stopLoader();
-        }
-        //TODO(hyunyoungs): stop the widgets loader when there is a rotation.
-
-        return Boolean.TRUE;
-    }
-
-    // We can't hide the IME if it was forced open.  So don't bother
-    @Override
-    public void onWindowFocusChanged(boolean hasFocus) {
-        super.onWindowFocusChanged(hasFocus);
-        mHasFocus = hasFocus;
-
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onWindowFocusChanged(hasFocus);
-        }
-    }
-
-    private boolean acceptFilter() {
-        final InputMethodManager inputManager = (InputMethodManager)
-                getSystemService(Context.INPUT_METHOD_SERVICE);
-        return !inputManager.isFullscreenMode();
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        final int uniChar = event.getUnicodeChar();
-        final boolean handled = super.onKeyDown(keyCode, event);
-        final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
-        if (!handled && acceptFilter() && isKeyNotWhitespace) {
-            boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
-                    keyCode, event);
-            if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
-                // something usable has been typed - start a search
-                // the typed text will be retrieved and cleared by
-                // showSearchDialog()
-                // If there are multiple keystrokes before the search dialog takes focus,
-                // onSearchRequested() will be called for every keystroke,
-                // but it is idempotent, so it's fine.
-                return onSearchRequested();
-            }
-        }
-
-        // Eat the long press event so the keyboard doesn't come up.
-        if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
-            return true;
-        }
-
-        return handled;
-    }
-
-    private String getTypedText() {
-        return mDefaultKeySsb.toString();
-    }
-
-    @Override
-    public void clearTypedText() {
-        mDefaultKeySsb.clear();
-        mDefaultKeySsb.clearSpans();
-        Selection.setSelection(mDefaultKeySsb, 0);
-    }
-
     public boolean isInState(LauncherState state) {
         return mStateManager.getState() == state;
     }
@@ -1002,7 +922,7 @@
         int stateOrdinal = savedState.getInt(RUNTIME_STATE, NORMAL.ordinal);
         LauncherState[] stateValues = LauncherState.values();
         LauncherState state = stateValues[stateOrdinal];
-        if (!state.doNotRestore) {
+        if (!state.disableRestore) {
             mStateManager.goToState(state, false /* animated */);
         }
 
@@ -1035,7 +955,7 @@
                 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
 
         // Setup the drag layer
-        mDragLayer.setup(this, mDragController, mAllAppsController);
+        mDragLayer.setup(this, mDragController);
 
         // Setup the hotseat
         mHotseat = (Hotseat) findViewById(R.id.hotseat);
@@ -1328,7 +1248,7 @@
         TraceHelper.beginSection("NEW_INTENT");
         super.onNewIntent(intent);
 
-        boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
+        boolean alreadyOnHome = hasWindowFocus() && ((intent.getFlags() &
                 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
                 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
 
@@ -1384,9 +1304,7 @@
     @Override
     public void onRestoreInstanceState(Bundle state) {
         super.onRestoreInstanceState(state);
-        for (int page: mSynchronouslyBoundPages) {
-            mWorkspace.restoreInstanceStateForChild(page);
-        }
+        mWorkspace.restoreInstanceStateForChild(mSynchronouslyBoundPage);
     }
 
     @Override
@@ -1450,7 +1368,6 @@
         } catch (NullPointerException ex) {
             Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
         }
-        mAppWidgetHost = null;
 
         TextKeyListener.getInstance().release();
         WallpaperColorInfo.getInstance(this).setOnThemeChangeListener(null);
@@ -1495,11 +1412,6 @@
     @Override
     public void startSearch(String initialQuery, boolean selectInitialQuery,
             Bundle appSearchData, boolean globalSearch) {
-
-        if (initialQuery == null) {
-            // Use any text typed in the launcher as the initial query
-            initialQuery = getTypedText();
-        }
         if (appSearchData == null) {
             appSearchData = new Bundle();
             appSearchData.putString("source", "launcher-search");
@@ -1508,69 +1420,13 @@
         if (mLauncherCallbacks == null ||
                 !mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData)) {
             // Starting search from the callbacks failed. Start the default global search.
-            startGlobalSearch(initialQuery, selectInitialQuery, appSearchData, null);
+            super.startSearch(initialQuery, selectInitialQuery, appSearchData, true);
         }
 
         // We need to show the workspace after starting the search
         mStateManager.goToState(NORMAL);
     }
 
-    /**
-     * Starts the global search activity. This code is a copied from SearchManager
-     */
-    public void startGlobalSearch(String initialQuery,
-            boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
-        final SearchManager searchManager =
-            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
-        ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
-        if (globalSearchActivity == null) {
-            Log.w(TAG, "No global search activity found.");
-            return;
-        }
-        Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.setComponent(globalSearchActivity);
-        // Make sure that we have a Bundle to put source in
-        if (appSearchData == null) {
-            appSearchData = new Bundle();
-        } else {
-            appSearchData = new Bundle(appSearchData);
-        }
-        // Set source to package name of app that starts global search if not set already.
-        if (!appSearchData.containsKey("source")) {
-            appSearchData.putString("source", getPackageName());
-        }
-        intent.putExtra(SearchManager.APP_DATA, appSearchData);
-        if (!TextUtils.isEmpty(initialQuery)) {
-            intent.putExtra(SearchManager.QUERY, initialQuery);
-        }
-        if (selectInitialQuery) {
-            intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
-        }
-        intent.setSourceBounds(sourceBounds);
-        try {
-            startActivity(intent);
-        } catch (ActivityNotFoundException ex) {
-            Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
-        }
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        super.onPrepareOptionsMenu(menu);
-        if (mLauncherCallbacks != null) {
-            return mLauncherCallbacks.onPrepareOptionsMenu(menu);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onSearchRequested() {
-        startSearch(null, false, null, true);
-        // Use a custom animation for launching search
-        return true;
-    }
-
     public boolean isWorkspaceLocked() {
         return mWorkspaceLoading || mPendingRequestArgs != null;
     }
@@ -2395,7 +2251,7 @@
         // we are starting a fresh bind, close all such panels as all the icons are about
         // to go away.
         AbstractFloatingView.closeOpenViews(this, true,
-                AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_WIDGETS_FULL_SHEET);
+                AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
 
         setWorkspaceLoading(true);
 
@@ -2737,7 +2593,7 @@
     }
 
     public void onPageBoundSynchronously(int page) {
-        mSynchronouslyBoundPages.add(page);
+        mSynchronouslyBoundPage = page;
     }
 
     @Override
@@ -2787,11 +2643,7 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     public void finishBindingItems() {
-        Runnable r = new Runnable() {
-            public void run() {
-                finishBindingItems();
-            }
-        };
+        Runnable r = this::finishBindingItems;
         if (waitUntilResume(r)) {
             return;
         }
@@ -3036,12 +2888,6 @@
         return mRotationEnabled;
     }
 
-    private boolean shouldShowDiscoveryBounce() {
-        return isInState(NORMAL)
-                && !mSharedPrefs.getBoolean(AllAppsState.APPS_VIEW_SHOWN, false)
-                && !UserManagerCompat.getInstance(this).isDemoUser();
-    }
-
     /**
      * $ adb shell dumpsys activity com.android.launcher3.Launcher [--all]
      */
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 928258f..914d9eb 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -48,10 +48,8 @@
     void onActivityResult(int requestCode, int resultCode, Intent data);
     void onRequestPermissionsResult(int requestCode, String[] permissions,
             int[] grantResults);
-    void onWindowFocusChanged(boolean hasFocus);
     void onAttachedToWindow();
     void onDetachedFromWindow();
-    boolean onPrepareOptionsMenu(Menu menu);
     void dump(String prefix, FileDescriptor fd, PrintWriter w, String[] args);
     void onHomeIntent();
     boolean handleBackPressed();
diff --git a/src/com/android/launcher3/LauncherExterns.java b/src/com/android/launcher3/LauncherExterns.java
index 887859c..272bbf6 100644
--- a/src/com/android/launcher3/LauncherExterns.java
+++ b/src/com/android/launcher3/LauncherExterns.java
@@ -24,11 +24,9 @@
  */
 public interface LauncherExterns {
 
-    public boolean setLauncherCallbacks(LauncherCallbacks callbacks);
+    boolean setLauncherCallbacks(LauncherCallbacks callbacks);
 
-    public SharedPreferences getSharedPrefs();
+    SharedPreferences getSharedPrefs();
 
-    public void setLauncherOverlay(Launcher.LauncherOverlay overlay);
-
-    void clearTypedText();
+    void setLauncherOverlay(Launcher.LauncherOverlay overlay);
 }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 01d53da..dfb935f 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -21,7 +21,7 @@
 
 import android.view.View;
 
-import com.android.launcher3.states.AllAppsState;
+import com.android.launcher3.uioverrides.AllAppsState;
 import com.android.launcher3.states.SpringLoadedState;
 import com.android.launcher3.uioverrides.OverviewState;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -37,13 +37,16 @@
     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;
-    protected static final int FLAG_DO_NOT_RESTORE = 1 << 3;
+    protected static final int FLAG_DISABLE_RESTORE = 1 << 3;
     protected static final int FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED = 1 << 4;
+    protected static final int FLAG_DISABLE_PAGE_CLIPPING = 1 << 5;
+
+    protected static final PageAlphaProvider DEFAULT_ALPHA_PROVIDER = (i) -> 1f;
 
     private static final LauncherState[] sAllStates = new LauncherState[4];
 
     public static final LauncherState NORMAL = new LauncherState(0, ContainerType.WORKSPACE,
-            0, 1f, FLAG_DO_NOT_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED);
+            0, 1f, FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED);
 
     public static final LauncherState ALL_APPS = new AllAppsState(1);
 
@@ -61,7 +64,7 @@
     /**
      * True if the state can be persisted across activity restarts.
      */
-    public final boolean doNotRestore;
+    public final boolean disableRestore;
 
     /**
      * True if workspace has multiple pages visible.
@@ -94,6 +97,12 @@
      */
     public final boolean workspaceIconsCanBeDragged;
 
+    /**
+     * True if the workspace pages should not be clipped relative to the workspace bounds
+     * for this state.
+     */
+    public final boolean disablePageClipping;
+
     public LauncherState(int id, int containerType, int transitionDuration, float verticalProgress,
             int flags) {
         this.containerType = containerType;
@@ -104,8 +113,9 @@
         this.workspaceAccessibilityFlag = (flags & FLAG_DISABLE_ACCESSIBILITY) != 0
                 ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
                 : IMPORTANT_FOR_ACCESSIBILITY_AUTO;
-        this.doNotRestore = (flags & FLAG_DO_NOT_RESTORE) != 0;
+        this.disableRestore = (flags & FLAG_DISABLE_RESTORE) != 0;
         this.workspaceIconsCanBeDragged = (flags & FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED) != 0;
+        this.disablePageClipping = (flags & FLAG_DISABLE_PAGE_CLIPPING) != 0;
 
         this.verticalProgress = verticalProgress;
 
@@ -135,7 +145,20 @@
         return launcher.getWorkspace().getCurrentPageDescription();
     }
 
+    public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
+        if (this != NORMAL || !launcher.getDeviceProfile().shouldFadeAdjacentWorkspaceScreens()) {
+            return DEFAULT_ALPHA_PROVIDER;
+        }
+        int centerPage = launcher.getWorkspace().getPageNearestToCenterOfScreen();
+        return (childIndex) ->  childIndex != centerPage ? 0 : 1f;
+    }
+
     protected static void dispatchWindowStateChanged(Launcher launcher) {
         launcher.getWindow().getDecorView().sendAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED);
     }
+
+    public interface PageAlphaProvider {
+
+        float getPageAlpha(int pageIndex);
+    }
 }
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 1e6016b..0137b26 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -25,9 +25,9 @@
 import android.os.Looper;
 import android.view.View;
 
-import com.android.launcher3.anim.AnimationLayerSet;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.uioverrides.UiFactory;
 
 /**
@@ -93,7 +93,7 @@
         return mState;
     }
 
-    private StateHandler[] getStateHandlers() {
+    public StateHandler[] getStateHandlers() {
         if (mStateHandlers == null) {
             mStateHandlers = UiFactory.getStateHandler(mLauncher);
         }
@@ -158,14 +158,14 @@
         mConfig.reset();
 
         if (!animated) {
-            setState(state);
+            onStateTransitionStart(state);
             for (StateHandler handler : getStateHandlers()) {
                 handler.setState(state);
             }
             if (mStateListener != null) {
                 mStateListener.onStateSetImmediately(state);
             }
-            mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+            onStateTransitionEnd(state);
 
             // Run any queued runnable
             if (onCompleteRunnable != null) {
@@ -178,7 +178,8 @@
         // transition plays in reverse and use the same duration as previous state.
         mConfig.duration = state == NORMAL ? mState.transitionDuration : state.transitionDuration;
 
-        AnimatorSet animation = createAnimationToNewWorkspaceInternal(state, onCompleteRunnable);
+        AnimatorSet animation = createAnimationToNewWorkspaceInternal(
+                state, new AnimatorSetBuilder(), onCompleteRunnable);
         Runnable runnable = new StartAnimRunnable(animation, state.getFinalFocus(mLauncher));
         if (delay > 0) {
             mUiHandler.postDelayed(runnable, delay);
@@ -196,28 +197,32 @@
      */
     public AnimatorPlaybackController createAnimationToNewWorkspace(
             LauncherState state, long duration) {
+        return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration);
+    }
+
+    public AnimatorPlaybackController createAnimationToNewWorkspace(
+            LauncherState state, AnimatorSetBuilder builder, long duration) {
         mConfig.reset();
         mConfig.userControlled = true;
         mConfig.duration = duration;
         return AnimatorPlaybackController.wrap(
-                createAnimationToNewWorkspaceInternal(state, null), duration);
+                createAnimationToNewWorkspaceInternal(state, builder, null), duration);
     }
 
     protected AnimatorSet createAnimationToNewWorkspaceInternal(final LauncherState state,
-            final Runnable onCompleteRunnable) {
-        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-        final AnimationLayerSet layerViews = new AnimationLayerSet();
-
+            AnimatorSetBuilder builder, final Runnable onCompleteRunnable) {
         for (StateHandler handler : getStateHandlers()) {
-            handler.setStateWithAnimation(state, layerViews, animation, mConfig);
+            builder.startTag(handler);
+            handler.setStateWithAnimation(state, builder, mConfig);
         }
-        animation.addListener(layerViews);
+
+        final AnimatorSet animation = builder.build();
         animation.addListener(new AnimationSuccessListener() {
 
             @Override
             public void onAnimationStart(Animator animation) {
                 // Change the internal state only when the transition actually starts
-                setState(state);
+                onStateTransitionStart(state);
                 if (mStateListener != null) {
                     mStateListener.onStateTransitionStart(state);
                 }
@@ -237,19 +242,28 @@
                 if (onCompleteRunnable != null) {
                     onCompleteRunnable.run();
                 }
-
-                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+                onStateTransitionEnd(state);
             }
         });
         mConfig.setAnimation(animation);
         return mConfig.mCurrentAnimation;
     }
 
-    private void setState(LauncherState state) {
+    private void onStateTransitionStart(LauncherState state) {
         mState.onStateDisabled(mLauncher);
         mState = state;
         mState.onStateEnabled(mLauncher);
         mLauncher.getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
+
+        if (state.disablePageClipping) {
+            // Only disable clipping if needed, otherwise leave it as previous value.
+            mLauncher.getWorkspace().setClipChildren(false);
+        }
+    }
+
+    private void onStateTransitionEnd(LauncherState state) {
+        mLauncher.getWorkspace().setClipChildren(!state.disablePageClipping);
+        mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
     }
 
     /**
@@ -321,8 +335,8 @@
         /**
          * Sets the UI to {@param state} by animating any changes.
          */
-        void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
-                AnimatorSet anim, AnimationConfig config);
+        void setStateWithAnimation(LauncherState toState,
+                AnimatorSetBuilder builder, AnimationConfig config);
     }
 
     public interface StateListener {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 9f6efb3..d6b5656 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3;
 
+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;
@@ -41,7 +44,6 @@
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.Interpolator;
 
@@ -521,9 +523,7 @@
     }
 
     private void sendScrollAccessibilityEvent() {
-        AccessibilityManager am =
-                (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
-        if (am.isEnabled()) {
+        if (isObservedEventType(getContext(), AccessibilityEvent.TYPE_VIEW_SCROLLED)) {
             if (mCurrentPage != getNextPage()) {
                 AccessibilityEvent ev =
                         AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
@@ -572,9 +572,7 @@
             }
 
             onPostReorderingAnimationCompleted();
-            AccessibilityManager am = (AccessibilityManager)
-                    getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
-            if (am.isEnabled()) {
+            if (isAccessibilityEnabled(getContext())) {
                 // Notify the user when the page changes
                 announceForAccessibility(getCurrentPageDescription());
             }
@@ -593,6 +591,11 @@
                 - mInsets.top - mInsets.bottom;
     }
 
+    public int getNormalChildWidth() {
+        return  getViewportWidth() - getPaddingLeft() - getPaddingRight()
+                - mInsets.left - mInsets.right;
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         if (getChildCount() == 0) {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index e6bc770..ca235eb 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -47,8 +47,6 @@
 import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
 
 import com.android.launcher3.config.FeatureFlags;
 
@@ -614,23 +612,6 @@
         return c == null || c.isEmpty();
     }
 
-    public static boolean isAccessibilityEnabled(Context context) {
-        AccessibilityManager accessibilityManager = (AccessibilityManager)
-                context.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        return accessibilityManager.isEnabled();
-    }
-
-    public static void sendCustomAccessibilityEvent(View target, int type, String text) {
-        AccessibilityManager accessibilityManager = (AccessibilityManager)
-                target.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
-        if (accessibilityManager.isEnabled()) {
-            AccessibilityEvent event = AccessibilityEvent.obtain(type);
-            target.onInitializeAccessibilityEvent(event);
-            event.getText().add(text);
-            accessibilityManager.sendAccessibilityEvent(event);
-        }
-    }
-
     public static boolean isBinderSizeError(Exception e) {
         return e.getCause() instanceof TransactionTooLargeException
                 || e.getCause() instanceof DeadObjectException;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 749afd0..417366d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -22,11 +22,10 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
-import static com.android.launcher3.Utilities.isAccessibilityEnabled;
+import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
@@ -63,7 +62,7 @@
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
 import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
-import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.badge.FolderBadgeInfo;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
@@ -193,10 +192,6 @@
     private static final int HOTSEAT_STATE_ALPHA_INDEX = 2;
 
     /**
-     * These values correspond to {@link Direction#X} & {@link Direction#Y}
-     */
-    private final float[] mPageAlpha = new float[] {1, 1};
-    /**
      * Hotseat alpha can be changed when moving horizontally, vertically, changing states.
      * The values correspond to {@link Direction#X}, {@link Direction#Y} &
      * {@link #HOTSEAT_STATE_ALPHA_INDEX} respectively.
@@ -436,7 +431,6 @@
         mCurrentPage = DEFAULT_PAGE;
         DeviceProfile grid = mLauncher.getDeviceProfile();
         setWillNotDraw(false);
-        setClipChildren(false);
         setClipToPadding(false);
 
         setupLayoutTransition();
@@ -1191,33 +1185,21 @@
         // TODO(adamcohen): figure out a final effect here. We may need to recommend
         // different effects based on device performance. On at least one relatively high-end
         // device I've tried, translating the launcher causes things to get quite laggy.
-        setWorkspaceTranslationAndAlpha(Direction.X, transX, alpha);
+        setWorkspaceTranslationAndAlpha(transX, alpha);
         setHotseatTranslationAndAlpha(Direction.X, transX, alpha);
     }
 
     /**
-     * Moves the workspace UI in the Y direction.
-     * @param translation the amount of shift.
-     * @param alpha the alpha for the workspace page
-     */
-    public void setWorkspaceYTranslationAndAlpha(float translation, float alpha) {
-        setWorkspaceTranslationAndAlpha(Direction.Y, translation, alpha);
-    }
-
-    /**
      * Moves the workspace UI in the provided direction.
-     * @param direction the direction to move the workspace
-     * @param translation the amount of shift.
+     * @param translation the amount of horizontal shift.
      * @param alpha the alpha for the workspace page
      */
-    private void setWorkspaceTranslationAndAlpha(Direction direction, float translation, float alpha) {
-        Property<View, Float> property = direction.viewProperty;
-        mPageAlpha[direction.ordinal()] = alpha;
-        float finalAlpha = mPageAlpha[0] * mPageAlpha[1];
+    private void setWorkspaceTranslationAndAlpha(float translation, float alpha) {
+        float finalAlpha = alpha;
 
         View currentChild = getChildAt(getCurrentPage());
         if (currentChild != null) {
-            property.set(currentChild, translation);
+            currentChild.setTranslationX(translation);
             currentChild.setAlpha(finalAlpha);
         }
 
@@ -1225,7 +1207,7 @@
         if (Float.compare(translation, 0) == 0) {
             for (int i = getChildCount() - 1; i >= 0; i--) {
                 View child = getChildAt(i);
-                property.set(child, translation);
+                child.setTranslationX(0);
                 child.setAlpha(finalAlpha);
             }
         }
@@ -1564,10 +1546,10 @@
      * Sets the current workspace {@link LauncherState}, then animates the UI
      */
     @Override
-    public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
-            AnimatorSet anim, AnimationConfig config) {
+    public void setStateWithAnimation(LauncherState toState,
+            AnimatorSetBuilder builder, AnimationConfig config) {
         StateTransitionListener listener = new StateTransitionListener(toState);
-        mStateTransitionAnimation.setStateWithAnimation(toState, anim, layerViews, config);
+        mStateTransitionAnimation.setStateWithAnimation(toState, builder, config);
 
         // Invalidate the pages now, so that we have the visible pages before the
         // animation is started
@@ -1579,8 +1561,8 @@
         ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
         stepAnimator.addUpdateListener(listener);
         stepAnimator.setDuration(config.duration);
-        anim.play(stepAnimator);
-        anim.addListener(listener);
+        stepAnimator.addListener(listener);
+        builder.play(stepAnimator);
     }
 
     public void updateAccessibilityFlags() {
@@ -3519,17 +3501,17 @@
 
             mRefreshPending = false;
 
-            mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
-                @Override
-                public boolean evaluate(ItemInfo info, View view) {
-                    if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
-                        mLauncher.removeItem(view, info, false /* deleteFromDb */);
-                        mLauncher.bindAppWidget((LauncherAppWidgetInfo) info);
-                    }
-                    // process all the shortcuts
-                    return false;
+            ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size());
+            mapOverItems(MAP_NO_RECURSE, (info, view) -> {
+                if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
+                    views.add((PendingAppWidgetHostView) view);
                 }
+                // process all children
+                return false;
             });
+            for (PendingAppWidgetHostView view : views) {
+                view.reinflate();
+            }
         }
 
         @Override
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 8edec40..9f76b6f 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -18,21 +18,19 @@
 
 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 android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
 import android.util.Property;
 import android.view.View;
-import android.view.accessibility.AccessibilityManager;
 
+import com.android.launcher3.LauncherState.PageAlphaProvider;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
-import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 
 /**
@@ -97,28 +95,23 @@
     private final Launcher mLauncher;
     private final Workspace mWorkspace;
 
-    private final boolean mWorkspaceFadeInAdjacentScreens;
-
     private float mNewScale;
 
     public WorkspaceStateTransitionAnimation(Launcher launcher, Workspace workspace) {
         mLauncher = launcher;
         mWorkspace = workspace;
-
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        Resources res = launcher.getResources();
-        mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha);
-        mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
+        mWorkspaceScrimAlpha = launcher.getResources()
+                .getInteger(R.integer.config_workspaceScrimAlpha);
     }
 
     public void setState(LauncherState toState) {
         setWorkspaceProperty(toState, NO_ANIM_PROPERTY_SETTER);
     }
 
-    public void setStateWithAnimation(LauncherState toState, AnimatorSet anim,
-            AnimationLayerSet layerViews, AnimationConfig config) {
+    public void setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder,
+            AnimationConfig config) {
         AnimatedPropertySetter propertySetter =
-                new AnimatedPropertySetter(config.duration, layerViews, anim);
+                new AnimatedPropertySetter(config.duration, builder);
         setWorkspaceProperty(toState, propertySetter);
     }
 
@@ -134,10 +127,10 @@
         mNewScale = scaleAndTranslationY[0];
         final float finalWorkspaceTranslationY = scaleAndTranslationY[1];
 
-        int toPage = mWorkspace.getPageNearestToCenterOfScreen();
+        PageAlphaProvider pageAlphaProvider = state.getWorkspacePageAlphaProvider(mLauncher);
         final int childCount = mWorkspace.getChildCount();
         for (int i = 0; i < childCount; i++) {
-            applyChildState(state, (CellLayout) mWorkspace.getChildAt(i), i, toPage,
+            applyChildState(state, (CellLayout) mWorkspace.getChildAt(i), i, pageAlphaProvider,
                     propertySetter);
         }
 
@@ -151,21 +144,16 @@
     }
 
     public void applyChildState(LauncherState state, CellLayout cl, int childIndex) {
-        applyChildState(state, cl, childIndex, mWorkspace.getPageNearestToCenterOfScreen(),
+        applyChildState(state, cl, childIndex, state.getWorkspacePageAlphaProvider(mLauncher),
                 NO_ANIM_PROPERTY_SETTER);
     }
 
     private void applyChildState(LauncherState state, CellLayout cl, int childIndex,
-            int centerPage, PropertySetter propertySetter) {
+            PageAlphaProvider pageAlphaProvider, PropertySetter propertySetter) {
         propertySetter.setInt(cl.getScrimBackground(),
                 DRAWABLE_ALPHA, state.hasScrim ? 255 : 0, Interpolators.ZOOM_IN);
-
-        // Only animate the page alpha when we actually fade pages
-        if (mWorkspaceFadeInAdjacentScreens) {
-            float finalAlpha = state == LauncherState.NORMAL && childIndex != centerPage ? 0 : 1f;
-            propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
-                    finalAlpha, Interpolators.ZOOM_IN);
-        }
+        propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
+                pageAlphaProvider.getPageAlpha(childIndex), Interpolators.DEACCEL_2);
     }
 
     public static class PropertySetter {
@@ -176,7 +164,7 @@
                 return;
             }
             view.setAlpha(alpha);
-            AlphaUpdateListener.updateVisibility(view, isAccessibilityEnabled(view));
+            AlphaUpdateListener.updateVisibility(view, isAccessibilityEnabled(view.getContext()));
         }
 
         public <T> void setFloat(T target, Property<T, Float> property, float value,
@@ -188,25 +176,16 @@
                 TimeInterpolator interpolator) {
             property.set(target, value);
         }
-
-        protected boolean isAccessibilityEnabled(View v) {
-            AccessibilityManager am = (AccessibilityManager)
-                    v.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
-            return am.isEnabled();
-        }
     }
 
     public static class AnimatedPropertySetter extends PropertySetter {
 
         private final long mDuration;
-        private final AnimationLayerSet mLayerViews;
-        private final AnimatorSet mStateAnimator;
+        private final AnimatorSetBuilder mStateAnimator;
 
-        public AnimatedPropertySetter(
-                long duration, AnimationLayerSet layerView, AnimatorSet anim) {
+        public AnimatedPropertySetter(long duration, AnimatorSetBuilder builder) {
             mDuration = duration;
-            mLayerViews = layerView;
-            mStateAnimator = anim;
+            mStateAnimator = builder;
         }
 
         @Override
@@ -216,11 +195,11 @@
                     return;
                 }
                 anim = ObjectAnimator.ofFloat(view, View.ALPHA, alpha);
-                anim.addListener(new AlphaUpdateListener(view, isAccessibilityEnabled(view)));
+                anim.addListener(new AlphaUpdateListener(view,
+                        isAccessibilityEnabled(view.getContext())));
             }
 
             anim.setDuration(mDuration).setInterpolator(getFadeInterpolator(alpha));
-            mLayerViews.addView(view);
             mStateAnimator.play(anim);
         }
 
diff --git a/src/com/android/launcher3/accessibility/DragViewStateAnnouncer.java b/src/com/android/launcher3/accessibility/DragViewStateAnnouncer.java
index 99deb7b..fdf2786 100644
--- a/src/com/android/launcher3/accessibility/DragViewStateAnnouncer.java
+++ b/src/com/android/launcher3/accessibility/DragViewStateAnnouncer.java
@@ -16,10 +16,10 @@
 
 package com.android.launcher3.accessibility;
 
-import android.content.Context;
+import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
+
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
 
 import com.android.launcher3.Launcher;
 
@@ -59,11 +59,6 @@
     }
 
     public static DragViewStateAnnouncer createFor(View v) {
-        if (((AccessibilityManager) v.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE))
-                .isEnabled()) {
-            return new DragViewStateAnnouncer(v);
-        } else {
-            return null;
-        }
+        return isAccessibilityEnabled(v.getContext()) ? new DragViewStateAnnouncer(v) : null;
     }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index f4026f2..7b89c5c 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -61,7 +61,6 @@
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.TransformingTouchDelegate;
 import com.android.launcher3.views.BottomUserEducationView;
-import com.android.launcher3.views.SlidingTabStrip;
 
 import java.util.HashMap;
 import java.util.List;
@@ -85,8 +84,7 @@
     private SearchUiManager mSearchUiManager;
     private View mSearchContainer;
     private InterceptingViewPager mViewPager;
-    private ViewGroup mHeader;
-    private FloatingHeaderHandler mFloatingHeaderHandler;
+    private FloatingHeaderView mHeader;
     private TabsPagerAdapter mTabsPagerAdapter;
 
     private SpannableStringBuilder mSearchQueryBuilder = null;
@@ -234,8 +232,8 @@
         for (int i = 0; i < mAH.length; i++) {
             updatePromiseAppProgress(app, mAH[i].recyclerView);
         }
-        if (mFloatingHeaderHandler != null) {
-            updatePromiseAppProgress(app, mFloatingHeaderHandler.getContentView());
+        if (isHeaderVisible()) {
+            updatePromiseAppProgress(app, mHeader.getPredictionRow());
         }
     }
 
@@ -262,9 +260,6 @@
         if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) {
             return true;
         }
-        if (mUsingTabs && mLauncher.getDragLayer().isEventOverView(mHeader, ev)) {
-            return true;
-        }
         AllAppsRecyclerView rv = getActiveRecyclerView();
         return rv == null || rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
     }
@@ -286,8 +281,8 @@
                 mAH[i].recyclerView.scrollToTop();
             }
         }
-        if (mFloatingHeaderHandler != null) {
-            mFloatingHeaderHandler.reset();
+        if (isHeaderVisible()) {
+            mHeader.reset();
         }
         // Reset the search bar and base recycler view after transitioning home
         mSearchUiManager.reset();
@@ -309,7 +304,6 @@
         });
 
         mHeader = findViewById(R.id.all_apps_header);
-        mFloatingHeaderHandler = new FloatingHeaderHandler(mHeader);
         rebindAdapters(mUsingTabs);
 
         mSearchContainer = findViewById(R.id.search_container_all_apps);
@@ -447,7 +441,6 @@
             if (FeatureFlags.ALL_APPS_PREDICTION_ROW_VIEW) {
                 setupHeader();
             } else {
-                mFloatingHeaderHandler = null;
                 mHeader.setVisibility(View.GONE);
             }
         }
@@ -489,29 +482,19 @@
         if (mTabsPagerAdapter != null) {
             return;
         }
-        final SlidingTabStrip tabs = findViewById(R.id.tabs);
+        final PersonalWorkSlidingTabStrip tabs = findViewById(R.id.tabs);
         mViewPager.setAdapter(mTabsPagerAdapter = new TabsPagerAdapter());
         mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
 
-            boolean mVisible = true;
-
             @Override
             public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                 tabs.updateIndicatorPosition(position, positionOffset);
-                if (positionOffset == 0 && !mVisible || positionOffset > 0 && mVisible) {
-                    mVisible = positionOffset == 0;
-                    for (int i = 0; i < mAH.length; i++) {
-                        if (mAH[i].recyclerView != null) {
-                            mAH[i].recyclerView.getScrollbar().setAlpha(mVisible ? 1 : 0);
-                        }
-                    }
-                }
             }
 
             @Override
             public void onPageSelected(int pos) {
                 tabs.updateTabTextColor(pos);
-                mFloatingHeaderHandler.setMainActive(pos == 0);
+                mHeader.setMainActive(pos == 0);
                 applyTouchDelegate();
                 if (mAH[pos].recyclerView != null) {
                     mAH[pos].recyclerView.bindFastScrollbar();
@@ -525,6 +508,7 @@
             public void onPageScrollStateChanged(int state) {
             }
         });
+        mAH[AdapterHolder.MAIN].recyclerView.bindFastScrollbar();
 
         findViewById(R.id.tab_personal)
                 .setOnClickListener((View view) -> mViewPager.setCurrentItem(0));
@@ -533,14 +517,16 @@
     }
 
     public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
-        if (mFloatingHeaderHandler != null) {
-            mFloatingHeaderHandler.getContentView().setPredictedApps(apps);
+        if (isHeaderVisible()) {
+            mHeader.getPredictionRow().setPredictedApps(apps);
         }
         mAH[AdapterHolder.MAIN].appsList.setPredictedApps(apps);
         boolean hasPredictions = !apps.isEmpty();
         if (mHasPredictions != hasPredictions) {
             mHasPredictions = hasPredictions;
-            setupHeader();
+            if (FeatureFlags.ALL_APPS_PREDICTION_ROW_VIEW) {
+                setupHeader();
+            }
         }
     }
 
@@ -556,29 +542,18 @@
         return mUsingTabs;
     }
 
-    public FloatingHeaderHandler getFloatingHeaderHandler() {
-        return mFloatingHeaderHandler;
+    public FloatingHeaderView getFloatingHeaderView() {
+        return mHeader;
     }
 
     private void setupHeader() {
-        if (mFloatingHeaderHandler == null) {
+        if (mHeader == null) {
             return;
         }
         mHeader.setVisibility(View.VISIBLE);
+        mHeader.setup(mAH, mComponentToAppMap, mNumPredictedAppsPerRow);
 
-        boolean usePredictionRow = mHasPredictions && !mSearchModeWhileUsingTabs;
-        int contentHeight = usePredictionRow ? mLauncher.getDeviceProfile().allAppsCellHeightPx : 0;;
-        if (usePredictionRow && !mUsingTabs) {
-            contentHeight += getResources()
-                    .getDimensionPixelSize(R.dimen.all_apps_prediction_row_divider_height);
-        }
-        RecyclerView mainRV = mAH[AdapterHolder.MAIN].recyclerView;
-        RecyclerView workRV = mAH[AdapterHolder.WORK].recyclerView;
-        mFloatingHeaderHandler.setup(mainRV, workRV, contentHeight);
-        mFloatingHeaderHandler.getContentView().setup(mAH[AdapterHolder.MAIN].adapter,
-                mComponentToAppMap, mNumPredictedAppsPerRow);
-
-        int padding = contentHeight;
+        int padding = mHeader.getPredictionRow().getExpectedHeight();
         if (mHasPredictions && !mUsingTabs) {
             padding += mHeader.getPaddingTop() + mHeader.getPaddingBottom();
         }
@@ -593,7 +568,7 @@
             mAH[i].adapter.setLastSearchQuery(query);
         }
         boolean hasQuery = !TextUtils.isEmpty(query);
-        if (mFloatingHeaderHandler != null && mUsingTabs && hasQuery) {
+        if (mUsingTabs && hasQuery) {
             mSearchModeWhileUsingTabs = true;
             rebindAdapters(false); // hide tabs
         } else if (mSearchModeWhileUsingTabs && !hasQuery) {
@@ -639,12 +614,16 @@
 
     public List<AppInfo> getPredictedApps() {
         if (mUsingTabs) {
-            return mFloatingHeaderHandler.getContentView().getPredictedApps();
+            return mHeader.getPredictionRow().getPredictedApps();
         } else {
             return mAH[AdapterHolder.MAIN].appsList.getPredictedApps();
         }
     }
 
+    private boolean isHeaderVisible() {
+        return mHeader != null && mHeader.getVisibility() == View.VISIBLE;
+    }
+
     public class AdapterHolder {
         public static final int MAIN = 0;
         public static final int WORK = 1;
@@ -694,8 +673,8 @@
                         ? paddingTopForTabs : padding.top;
                 recyclerView.setPadding(padding.left, paddingTop, padding.right, padding.bottom);
             }
-            if (mFloatingHeaderHandler != null) {
-                mFloatingHeaderHandler.getContentView()
+            if (isHeaderVisible()) {
+                mHeader.getPredictionRow()
                         .setPadding(padding.left, 0 , padding.right, 0);
             }
         }
@@ -707,8 +686,8 @@
                 }
                 adapter.setNumAppsPerRow(mNumAppsPerRow);
                 appsList.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
-                if (mFloatingHeaderHandler != null) {
-                    mFloatingHeaderHandler.getContentView()
+                if (isHeaderVisible()) {
+                    mHeader.getPredictionRow()
                             .setNumAppsPerRow(mNumPredictedAppsPerRow);
                 }
             }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 5789b67..fd80784 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -31,7 +31,6 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.config.FeatureFlags;
@@ -53,7 +52,6 @@
     private AlphabeticalAppsList mApps;
     private AllAppsFastScrollHelper mFastScrollHelper;
     private int mNumAppsPerRow;
-    private int mUserProfileTabContentHeight;
 
     // The specific view heights that we use to calculate scroll
     private SparseIntArray mViewHeights = new SparseIntArray();
@@ -127,8 +125,6 @@
     public void setApps(AlphabeticalAppsList apps, boolean usingTabs) {
         mApps = apps;
         mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
-        mUserProfileTabContentHeight = usingTabs
-                ? Launcher.getLauncher(getContext()).getDeviceProfile().allAppsCellHeightPx : 0;;
     }
 
     public AlphabeticalAppsList getApps() {
@@ -362,6 +358,9 @@
      */
     @Override
     public void onUpdateScrollbar(int dy) {
+        if (mApps == null) {
+            return;
+        }
         List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
 
         // Skip early if there are no items or we haven't been measured
@@ -488,18 +487,11 @@
     @Override
     protected int getAvailableScrollHeight() {
         return getPaddingTop() + getCurrentScrollY(getAdapter().getItemCount(), 0)
-                - getHeight() + getPaddingBottom() + mUserProfileTabContentHeight;
+                - getHeight() + getPaddingBottom();
     }
 
     public int getScrollBarTop() {
-        return super.getScrollBarTop() + mUserProfileTabContentHeight;
-    }
-
-    /**
-     * Returns the height of the fast scroll bar
-     */
-    public int getScrollbarTrackHeight() {
-        return super.getScrollbarTrackHeight() + mUserProfileTabContentHeight;
+        return getResources().getDimensionPixelOffset(R.dimen.all_apps_header_top_padding);
     }
 
     public RecyclerViewFastScroller getScrollbar() {
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index eb26704..5de58b4 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -4,9 +4,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 
 import android.animation.Animator;
-import android.animation.AnimatorInflater;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.util.Property;
 import android.view.View;
@@ -20,8 +18,8 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.anim.AnimationLayerSet;
 import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.graphics.GradientView;
 import com.android.launcher3.util.SystemUiController;
@@ -54,10 +52,9 @@
         }
     };
 
-    private final Interpolator mWorkspaceAccelnterpolator = Interpolators.ACCEL_2;
     private final Interpolator mHotseatAccelInterpolator = Interpolators.ACCEL_1_5;
 
-    private static final float PARALLAX_COEFFICIENT = .125f;
+    public static final float PARALLAX_COEFFICIENT = .125f;
 
     private AllAppsContainerView mAppsView;
     private Workspace mWorkspace;
@@ -77,8 +74,6 @@
 
     private static final float DEFAULT_SHIFT_RANGE = 10;
 
-    private boolean mIsTranslateWithoutWorkspace = false;
-    private Animator mDiscoBounceAnimation;
     private GradientView mGradientView;
 
     public AllAppsTransitionController(Launcher l) {
@@ -126,7 +121,7 @@
      * @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace
      *
      * @see #setState(LauncherState)
-     * @see #setStateWithAnimation(LauncherState, AnimationLayerSet, AnimatorSet, AnimationConfig)
+     * @see #setStateWithAnimation(LauncherState, AnimatorSetBuilder, AnimationConfig)
      */
     public void setProgress(float progress) {
         mProgress = progress;
@@ -134,7 +129,6 @@
 
         float workspaceHotseatAlpha = Utilities.boundToRange(progress, 0f, 1f);
         float alpha = 1 - workspaceHotseatAlpha;
-        float workspaceAlpha = mWorkspaceAccelnterpolator.getInterpolation(workspaceHotseatAlpha);
         float hotseatAlpha = mHotseatAccelInterpolator.getInterpolation(workspaceHotseatAlpha);
 
         updateAllAppsBg(alpha);
@@ -150,12 +144,6 @@
                     hotseatAlpha);
         }
 
-        if (mIsTranslateWithoutWorkspace) {
-            return;
-        }
-        mWorkspace.setWorkspaceYTranslationAndAlpha(
-                PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent), workspaceAlpha);
-
         updateLightStatusBar(shiftCurrent);
     }
 
@@ -178,8 +166,8 @@
      * dependent UI using various animation events
      */
     @Override
-    public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
-            AnimatorSet animationOut, AnimationConfig config) {
+    public void setStateWithAnimation(LauncherState toState,
+            AnimatorSetBuilder builder, AnimationConfig config) {
         if (Float.compare(mProgress, toState.verticalProgress) == 0) {
             // Fail fast
             onProgressAnimationEnd();
@@ -191,7 +179,13 @@
                 this, PROGRESS, mProgress, toState.verticalProgress);
         anim.setDuration(config.duration);
         anim.setInterpolator(interpolator);
-        anim.addListener(new AnimationSuccessListener() {
+        anim.addListener(getProgressAnimatorListener());
+
+        builder.play(anim);
+    }
+
+    public AnimatorListenerAdapter getProgressAnimatorListener() {
+        return new AnimationSuccessListener() {
             @Override
             public void onAnimationSuccess(Animator animator) {
                 onProgressAnimationEnd();
@@ -201,50 +195,7 @@
             public void onAnimationStart(Animator animation) {
                 onProgressAnimationStart();
             }
-        });
-
-        animationOut.play(anim);
-    }
-
-    public void showDiscoveryBounce() {
-        // cancel existing animation in case user locked and unlocked at a super human speed.
-        cancelDiscoveryAnimation();
-
-        // assumption is that this variable is always null
-        mDiscoBounceAnimation = AnimatorInflater.loadAnimator(mLauncher,
-                R.animator.discovery_bounce);
-        mDiscoBounceAnimation.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animator) {
-                mIsTranslateWithoutWorkspace = true;
-                onProgressAnimationStart();
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animator) {
-                onProgressAnimationEnd();
-                mDiscoBounceAnimation = null;
-                mIsTranslateWithoutWorkspace = false;
-            }
-        });
-        mDiscoBounceAnimation.setTarget(this);
-        mAppsView.post(new Runnable() {
-            @Override
-            public void run() {
-                if (mDiscoBounceAnimation == null) {
-                    return;
-                }
-                mDiscoBounceAnimation.start();
-            }
-        });
-    }
-
-    public void cancelDiscoveryAnimation() {
-        if (mDiscoBounceAnimation == null) {
-            return;
-        }
-        mDiscoBounceAnimation.cancel();
-        mDiscoBounceAnimation = null;
+        };
     }
 
     public void setupViews(AllAppsContainerView appsView, Hotseat hotseat, Workspace workspace) {
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
new file mode 100644
index 0000000..550fcf9
--- /dev/null
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -0,0 +1,109 @@
+/*
+ * 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.allapps;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.view.MotionEvent;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.compat.UserManagerCompat;
+
+/**
+ * Floating view responsible for showing discovery bounce animation
+ */
+public class DiscoveryBounce extends AbstractFloatingView {
+
+    public static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown";
+
+    private final Launcher mLauncher;
+    private final Animator mDiscoBounceAnimation;
+
+    public DiscoveryBounce(Launcher launcher) {
+        super(launcher, null);
+        mLauncher = launcher;
+
+        mDiscoBounceAnimation = AnimatorInflater.loadAnimator(mLauncher,
+                R.animator.discovery_bounce);
+        AllAppsTransitionController controller = mLauncher.getAllAppsController();
+        mDiscoBounceAnimation.setTarget(controller);
+        mDiscoBounceAnimation.addListener(controller.getProgressAnimatorListener());
+
+        mDiscoBounceAnimation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                handleClose(false);
+            }
+        });
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mDiscoBounceAnimation.start();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mDiscoBounceAnimation.isRunning()) {
+            mDiscoBounceAnimation.end();
+        }
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        handleClose(false);
+        return false;
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        if (mIsOpen) {
+            mIsOpen = false;
+            mLauncher.getDragLayer().removeView(this);
+        }
+    }
+
+    @Override
+    public void logActionCommand(int command) {
+        // Since this is on-boarding popup, it is not a user controlled action.
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_ON_BOARD_POPUP) != 0;
+    }
+
+    public static void showIfNeeded(Launcher launcher) {
+        if (!launcher.isInState(NORMAL)
+                || launcher.getSharedPrefs().getBoolean(APPS_VIEW_SHOWN, false)
+                || AbstractFloatingView.getTopOpenView(launcher) != null
+                || UserManagerCompat.getInstance(launcher).isDemoUser()) {
+            return;
+        }
+
+        DiscoveryBounce view = new DiscoveryBounce(launcher);
+        view.mIsOpen = true;
+        launcher.getDragLayer().addView(view);
+    }
+}
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderHandler.java b/src/com/android/launcher3/allapps/FloatingHeaderHandler.java
deleted file mode 100644
index c4b533b..0000000
--- a/src/com/android/launcher3/allapps/FloatingHeaderHandler.java
+++ /dev/null
@@ -1,215 +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.allapps;
-
-import android.animation.ValueAnimator;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.RelativeLayout;
-
-import com.android.launcher3.R;
-
-public class FloatingHeaderHandler extends RecyclerView.OnScrollListener
-        implements ValueAnimator.AnimatorUpdateListener {
-
-    private final View mHeaderView;
-    private final PredictionRowView mPredictionRow;
-    private final ViewGroup mTabLayout;
-    private final View mDivider;
-    private final Rect mClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
-    private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
-
-    private RecyclerView mMainRV;
-    private RecyclerView mWorkRV;
-    private boolean mTopOnlyMode;
-    private boolean mHeaderHidden;
-    private int mMaxTranslation;
-    private int mSnappedScrolledY;
-    private int mTranslationY;
-    private int mMainScrolledY;
-    private int mWorkScrolledY;
-    private boolean mMainRVActive;
-
-    public FloatingHeaderHandler(@NonNull ViewGroup header) {
-        mHeaderView = header;
-        mTabLayout = header.findViewById(R.id.tabs);
-        mDivider = header.findViewById(R.id.divider);
-        mPredictionRow = header.findViewById(R.id.header_content);
-    }
-
-    public void setup(@NonNull RecyclerView personalRV, @Nullable RecyclerView workRV,
-        int predictionRowHeight) {
-        mTopOnlyMode = workRV == null;
-        mTabLayout.setVisibility(mTopOnlyMode ? View.GONE : View.VISIBLE);
-        mPredictionRow.getLayoutParams().height = predictionRowHeight;
-        mMaxTranslation = predictionRowHeight;
-        mMainRV = personalRV;
-        mMainRV.addOnScrollListener(this);
-        mWorkRV = workRV;
-        if (workRV != null) {
-            workRV.addOnScrollListener(this);
-        }
-        setMainActive(true);
-        setupDivider();
-    }
-
-    private void setupDivider() {
-        Resources res = mHeaderView.getResources();
-        int verticalGap = res.getDimensionPixelSize(R.dimen.all_apps_divider_margin_vertical);
-        int sideGap = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
-        mDivider.setPadding(sideGap, verticalGap,sideGap, mTopOnlyMode ? verticalGap : 0);
-        RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mDivider.getLayoutParams();
-        lp.removeRule(RelativeLayout.ALIGN_BOTTOM);
-        lp.addRule(RelativeLayout.ALIGN_BOTTOM, mTopOnlyMode ? R.id.header_content : R.id.tabs);
-        mDivider.setLayoutParams(lp);
-    }
-
-    public void setMainActive(boolean active) {
-        mMainRVActive = active;
-        mSnappedScrolledY = getCurrentScroll() - mMaxTranslation;
-        setExpanded(true);
-    }
-
-    public View getHeaderView() {
-        return mHeaderView;
-    }
-
-    public PredictionRowView getContentView() {
-        return mPredictionRow;
-    }
-
-    public ViewGroup getTabLayout() {
-        return mTabLayout;
-    }
-
-    public View getDivider() {
-        return mDivider;
-    }
-
-    @Override
-    public void onScrolled(RecyclerView rv, int dx, int dy) {
-        boolean isMainRV = rv == mMainRV;
-        if (isMainRV != mMainRVActive) {
-            return;
-        }
-
-        if (mAnimator.isStarted()) {
-            mAnimator.cancel();
-        }
-
-        int current = isMainRV
-                ? (mMainScrolledY -= dy)
-                : (mWorkScrolledY -= dy);
-
-        moved(current);
-        apply();
-    }
-
-    public void reset() {
-        mMainScrolledY = 0;
-        mWorkScrolledY = 0;
-        setExpanded(true);
-    }
-
-    private boolean canSnapAt(int currentScrollY) {
-        return !mTopOnlyMode || Math.abs(currentScrollY) <= mPredictionRow.getHeight();
-    }
-
-    private void moved(final int currentScrollY) {
-        if (mHeaderHidden) {
-            if (currentScrollY <= mSnappedScrolledY) {
-                if (canSnapAt(currentScrollY)) {
-                    mSnappedScrolledY = currentScrollY;
-                }
-            } else {
-                mHeaderHidden = false;
-            }
-            mTranslationY = currentScrollY;
-        } else if (!mHeaderHidden) {
-            mTranslationY = currentScrollY - mSnappedScrolledY - mMaxTranslation;
-
-            // update state vars
-            if (mTranslationY >= 0) { // expanded: must not move down further
-                mTranslationY = 0;
-                mSnappedScrolledY = currentScrollY - mMaxTranslation;
-            } else if (mTranslationY <= -mMaxTranslation) { // hide or stay hidden
-                mHeaderHidden = true;
-                mSnappedScrolledY = currentScrollY;
-            }
-        }
-    }
-
-    private void apply() {
-        int uncappedTranslationY = mTranslationY;
-        mTranslationY = Math.max(mTranslationY, -mMaxTranslation);
-        if (mTranslationY != uncappedTranslationY) {
-            // we hide it completely if already capped (for opening search anim)
-            mPredictionRow.setVisibility(View.INVISIBLE);
-        } else {
-            mPredictionRow.setVisibility(View.VISIBLE);
-            mPredictionRow.setTranslationY(uncappedTranslationY);
-        }
-        mTabLayout.setTranslationY(mTranslationY);
-        mDivider.setTranslationY(mTopOnlyMode ? uncappedTranslationY : mTranslationY);
-        mClip.top = mMaxTranslation + mTranslationY;
-        // clipping on a draw might cause additional redraw
-        mMainRV.setClipBounds(mClip);
-        if (mWorkRV != null) {
-            mWorkRV.setClipBounds(mClip);
-        }
-    }
-
-    @Override
-    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
-        if (!mTopOnlyMode && newState == RecyclerView.SCROLL_STATE_IDLE
-                && mTranslationY != -mMaxTranslation && mTranslationY != 0) {
-            float scroll = Math.abs(getCurrentScroll());
-            boolean expand =  scroll > mMaxTranslation
-                    ? Math.abs(mTranslationY) < mMaxTranslation / 2 : true;
-            setExpanded(expand);
-        }
-    }
-
-    private void setExpanded(boolean expand) {
-        int translateTo = expand ? 0 : -mMaxTranslation;
-        mAnimator.setIntValues(mTranslationY, translateTo);
-        mAnimator.addUpdateListener(this);
-        mAnimator.setDuration(150);
-        mAnimator.start();
-        mHeaderHidden = !expand;
-        mSnappedScrolledY = expand ? getCurrentScroll() - mMaxTranslation : getCurrentScroll();
-    }
-
-    public boolean isExpanded() {
-        return !mHeaderHidden;
-    }
-
-    private int getCurrentScroll() {
-        return mMainRVActive ? mMainScrolledY : mWorkScrolledY;
-    }
-
-    @Override
-    public void onAnimationUpdate(ValueAnimator animation) {
-        mTranslationY = (Integer) animation.getAnimatedValue();
-        apply();
-    }
-
-}
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
new file mode 100644
index 0000000..dc3afb5
--- /dev/null
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -0,0 +1,251 @@
+/*
+ * 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.allapps;
+
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RelativeLayout;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.util.ComponentKey;
+
+import java.util.HashMap;
+
+public class FloatingHeaderView extends RelativeLayout implements
+        ValueAnimator.AnimatorUpdateListener {
+
+
+    private final Rect mClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
+    private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
+    private final Point mTempOffset = new Point();
+    private final RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
+        @Override
+        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+        }
+
+        @Override
+        public void onScrolled(RecyclerView rv, int dx, int dy) {
+            if (rv != mCurrentRV) {
+                return;
+            }
+
+            if (mAnimator.isStarted()) {
+                mAnimator.cancel();
+            }
+
+            int current = -mCurrentRV.getCurrentScrollY();
+            moved(current);
+            apply();
+        }
+    };
+
+    private PredictionRowView mPredictionRow;
+    private ViewGroup mTabLayout;
+    private View mDivider;
+    private AllAppsRecyclerView mMainRV;
+    private AllAppsRecyclerView mWorkRV;
+    private AllAppsRecyclerView mCurrentRV;
+    private ViewGroup mParent;
+    private boolean mTabsHidden;
+    private boolean mHeaderCollapsed;
+    private int mMaxTranslation;
+    private int mSnappedScrolledY;
+    private int mTranslationY;
+    private boolean mForwardToRecyclerView;
+
+    public FloatingHeaderView(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public FloatingHeaderView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mTabLayout = findViewById(R.id.tabs);
+        mDivider = findViewById(R.id.divider);
+        mPredictionRow = findViewById(R.id.header_content);
+    }
+
+    public void setup(AllAppsContainerView.AdapterHolder[] mAH,
+            HashMap<ComponentKey, AppInfo> componentToAppMap, int numPredictedAppsPerRow) {
+        mTabsHidden = mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView == null;
+        mTabLayout.setVisibility(mTabsHidden ? View.GONE : View.VISIBLE);
+        mPredictionRow.setPadding(0, 0, 0, mTabsHidden ? getResources()
+                .getDimensionPixelSize(R.dimen.all_apps_prediction_row_divider_height) : 0);
+        mPredictionRow.setup(mAH[AllAppsContainerView.AdapterHolder.MAIN].adapter,
+                componentToAppMap, numPredictedAppsPerRow);
+        mMaxTranslation = mPredictionRow.getExpectedHeight();
+        mMainRV = setupRV(mMainRV, mAH[AllAppsContainerView.AdapterHolder.MAIN].recyclerView);
+        mWorkRV = setupRV(mWorkRV, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView);
+        mParent = (ViewGroup) mMainRV.getParent();
+        setMainActive(true);
+        setupDivider();
+    }
+
+    private AllAppsRecyclerView setupRV(AllAppsRecyclerView old, AllAppsRecyclerView updated) {
+        if (old != updated && updated != null ) {
+            updated.addOnScrollListener(mOnScrollListener);
+        }
+        return updated;
+    }
+
+    private void setupDivider() {
+        Resources res = getResources();
+        int verticalGap = res.getDimensionPixelSize(R.dimen.all_apps_divider_margin_vertical);
+        int sideGap = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
+        mDivider.setPadding(sideGap, verticalGap,sideGap, mTabsHidden ? verticalGap : 0);
+        RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mDivider.getLayoutParams();
+        lp.removeRule(RelativeLayout.ALIGN_BOTTOM);
+        lp.addRule(RelativeLayout.ALIGN_BOTTOM, mTabsHidden ? R.id.header_content : R.id.tabs);
+        mDivider.setLayoutParams(lp);
+    }
+
+    public void setMainActive(boolean active) {
+        mCurrentRV = active ? mMainRV : mWorkRV;
+        mSnappedScrolledY = mCurrentRV.getCurrentScrollY() - mMaxTranslation;
+        setExpanded(true);
+    }
+
+    public PredictionRowView getPredictionRow() {
+        return mPredictionRow;
+    }
+
+    public View getDivider() {
+        return mDivider;
+    }
+
+    public void reset() {
+        setExpanded(true);
+    }
+
+    private boolean canSnapAt(int currentScrollY) {
+        return Math.abs(currentScrollY) <= mPredictionRow.getHeight();
+    }
+
+    private void moved(final int currentScrollY) {
+        if (mHeaderCollapsed) {
+            if (currentScrollY <= mSnappedScrolledY) {
+                if (canSnapAt(currentScrollY)) {
+                    mSnappedScrolledY = currentScrollY;
+                }
+            } else {
+                mHeaderCollapsed = false;
+            }
+            mTranslationY = currentScrollY;
+        } else if (!mHeaderCollapsed) {
+            mTranslationY = currentScrollY - mSnappedScrolledY - mMaxTranslation;
+
+            // update state vars
+            if (mTranslationY >= 0) { // expanded: must not move down further
+                mTranslationY = 0;
+                mSnappedScrolledY = currentScrollY - mMaxTranslation;
+            } else if (mTranslationY <= -mMaxTranslation) { // hide or stay hidden
+                mHeaderCollapsed = true;
+                mSnappedScrolledY = currentScrollY;
+            }
+        }
+    }
+
+    private void apply() {
+        int uncappedTranslationY = mTranslationY;
+        mTranslationY = Math.max(mTranslationY, -mMaxTranslation);
+        if (mTranslationY != uncappedTranslationY) {
+            // we hide it completely if already capped (for opening search anim)
+            mPredictionRow.setVisibility(View.INVISIBLE);
+        } else {
+            mPredictionRow.setVisibility(View.VISIBLE);
+            mPredictionRow.setTranslationY(uncappedTranslationY);
+        }
+        mTabLayout.setTranslationY(mTranslationY);
+        mDivider.setTranslationY(mTabsHidden ? uncappedTranslationY : mTranslationY);
+        mClip.top = mMaxTranslation + mTranslationY;
+        // clipping on a draw might cause additional redraw
+        mMainRV.setClipBounds(mClip);
+        if (mWorkRV != null) {
+            mWorkRV.setClipBounds(mClip);
+        }
+    }
+
+    private void setExpanded(boolean expand) {
+        int translateTo = expand ? 0 : -mMaxTranslation;
+        mAnimator.setIntValues(mTranslationY, translateTo);
+        mAnimator.addUpdateListener(this);
+        mAnimator.setDuration(150);
+        mAnimator.start();
+        mHeaderCollapsed = !expand;
+        mSnappedScrolledY = expand
+                ? mCurrentRV.getCurrentScrollY() - mMaxTranslation
+                : mCurrentRV.getCurrentScrollY();
+    }
+
+    public boolean isExpanded() {
+        return !mHeaderCollapsed;
+    }
+
+    @Override
+    public void onAnimationUpdate(ValueAnimator animation) {
+        mTranslationY = (Integer) animation.getAnimatedValue();
+        apply();
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        calcOffset(mTempOffset);
+        ev.offsetLocation(mTempOffset.x, mTempOffset.y);
+        mForwardToRecyclerView = mCurrentRV.onInterceptTouchEvent(ev);
+        ev.offsetLocation(-mTempOffset.x, -mTempOffset.y);
+        return mForwardToRecyclerView || super.onInterceptTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mForwardToRecyclerView) {
+            // take this view's and parent view's (view pager) location into account
+            calcOffset(mTempOffset);
+            event.offsetLocation(mTempOffset.x, mTempOffset.y);
+            try {
+                return mCurrentRV.onTouchEvent(event);
+            } finally {
+                event.offsetLocation(-mTempOffset.x, -mTempOffset.y);
+            }
+        } else {
+            return super.onTouchEvent(event);
+        }
+    }
+
+    private void calcOffset(Point p) {
+        p.x = getLeft() - mCurrentRV.getLeft() - mParent.getLeft();
+        p.y = getTop() - mCurrentRV.getTop() - mParent.getTop();
+    }
+
+}
+
+
diff --git a/src/com/android/launcher3/views/SlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
similarity index 64%
rename from src/com/android/launcher3/views/SlidingTabStrip.java
rename to src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index c2ef41b..54948b0 100644
--- a/src/com/android/launcher3/views/SlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.views;
+package com.android.launcher3.allapps;
 
 import android.content.Context;
 import android.graphics.Canvas;
@@ -26,25 +26,39 @@
 import android.widget.LinearLayout;
 
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Themes;
 
-public class SlidingTabStrip extends LinearLayout {
+/**
+ * Supports two indicator colors, dedicated for personal and work tabs.
+ */
+public class PersonalWorkSlidingTabStrip extends LinearLayout {
+    private final Paint mPersonalTabIndicatorPaint;
+    private final Paint mWorkTabIndicatorPaint;
 
-    private final Paint mSelectedIndicatorPaint;
     private int mSelectedIndicatorHeight;
     private int mIndicatorLeft = -1;
     private int mIndicatorRight = -1;
     private int mSelectedPosition = 0;
     private float mSelectionOffset;
+    private boolean mIsRtl;
 
-    public SlidingTabStrip(@NonNull Context context, @Nullable AttributeSet attrs) {
+    public PersonalWorkSlidingTabStrip(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         setOrientation(HORIZONTAL);
         setWillNotDraw(false);
-        mSelectedIndicatorPaint = new Paint();
-        mSelectedIndicatorPaint.setColor(Themes.getAttrColor(context, android.R.attr.colorAccent));
-        mSelectedIndicatorHeight = getResources()
-                .getDimensionPixelSize(R.dimen.all_apps_tabs_indicator_height);
+
+        mSelectedIndicatorHeight =
+                getResources().getDimensionPixelSize(R.dimen.all_apps_tabs_indicator_height);
+
+        mPersonalTabIndicatorPaint = new Paint();
+        mPersonalTabIndicatorPaint.setColor(
+                Themes.getAttrColor(context, android.R.attr.colorAccent));
+
+        mWorkTabIndicatorPaint = new Paint();
+        mWorkTabIndicatorPaint.setColor(getResources().getColor(R.color.work_profile_color));
+
+        mIsRtl = Utilities.isRtl(getResources());
     }
 
     public void updateIndicatorPosition(int position, float positionOffset) {
@@ -54,7 +68,7 @@
     }
 
     public void updateTabTextColor(int pos) {
-        for (int i=0; i < getChildCount(); i++) {
+        for (int i = 0; i < getChildCount(); i++) {
             Button tab = (Button) getChildAt(i);
             tab.setSelected(i == pos);
         }
@@ -101,7 +115,20 @@
     @Override
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
-        canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
-                mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
+
+        final float middleX = getWidth() / 2.0f;
+        if (mIndicatorLeft <= middleX) {
+            canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
+                    middleX, getHeight(), getPaint(true /* firstHalf */));
+        }
+        if (mIndicatorRight > middleX) {
+            canvas.drawRect(middleX, getHeight() - mSelectedIndicatorHeight,
+                    mIndicatorRight, getHeight(), getPaint(false /* firstHalf */));
+        }
     }
-}
\ No newline at end of file
+
+    private Paint getPaint(boolean firstHalf) {
+        boolean isPersonal = mIsRtl ^ firstHalf;
+        return isPersonal ? mPersonalTabIndicatorPaint : mWorkTabIndicatorPaint;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/PredictionRowView.java b/src/com/android/launcher3/allapps/PredictionRowView.java
index ea91770..1aee948 100644
--- a/src/com/android/launcher3/allapps/PredictionRowView.java
+++ b/src/com/android/launcher3/allapps/PredictionRowView.java
@@ -26,6 +26,7 @@
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ComponentKeyMapper;
@@ -57,11 +58,28 @@
         setOrientation(LinearLayout.HORIZONTAL);
     }
 
-    public void setup(AllAppsGridAdapter adapter,
-            HashMap<ComponentKey, AppInfo> componentToAppMap, int numPredictedAppsPerRow) {
+    public void setup(AllAppsGridAdapter adapter, HashMap<ComponentKey, AppInfo> componentToAppMap,
+                      int numPredictedAppsPerRow) {
         mAdapter = adapter;
         mComponentToAppMap = componentToAppMap;
         mNumPredictedAppsPerRow = numPredictedAppsPerRow;
+        setVisibility(mPredictedAppComponents.isEmpty() ? View.GONE : View.VISIBLE);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(getExpectedHeight(),
+                MeasureSpec.EXACTLY));
+    }
+
+    public int getExpectedHeight() {
+        int height = 0;
+        if (!mPredictedAppComponents.isEmpty()) {
+            height += Launcher.getLauncher(getContext())
+                    .getDeviceProfile().allAppsCellHeightPx;
+            height += getPaddingTop() + getPaddingBottom();
+        }
+        return height;
     }
 
     /**
@@ -93,6 +111,7 @@
     public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
         mPredictedAppComponents.clear();
         mPredictedAppComponents.addAll(apps);
+        mPredictedApps.clear();
         mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents));
         onAppsUpdated();
     }
diff --git a/src/com/android/launcher3/anim/AnimationLayerSet.java b/src/com/android/launcher3/anim/AnimationLayerSet.java
deleted file mode 100644
index f0b3458..0000000
--- a/src/com/android/launcher3/anim/AnimationLayerSet.java
+++ /dev/null
@@ -1,69 +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.anim;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.util.ArrayMap;
-import android.view.View;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Helper class to automatically build view hardware layers for the duration of an animation.
- */
-public class AnimationLayerSet extends AnimatorListenerAdapter {
-
-    private final ArrayMap<View, Integer> mViewsToLayerTypeMap;
-
-    public AnimationLayerSet() {
-        mViewsToLayerTypeMap = new ArrayMap<>();
-    }
-
-    public AnimationLayerSet(View v) {
-        mViewsToLayerTypeMap = new ArrayMap<>(1);
-        addView(v);
-    }
-
-    public void addView(View v) {
-        mViewsToLayerTypeMap.put(v, v.getLayerType());
-    }
-
-    @Override
-    public void onAnimationStart(Animator animation) {
-        // Enable all necessary layers
-        Iterator<Map.Entry<View, Integer>> itr = mViewsToLayerTypeMap.entrySet().iterator();
-        while (itr.hasNext()) {
-            Map.Entry<View, Integer> entry = itr.next();
-            View v = entry.getKey();
-            entry.setValue(v.getLayerType());
-            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-            if (v.isAttachedToWindow() && v.getVisibility() == View.VISIBLE) {
-                v.buildLayer();
-            }
-        }
-    }
-
-    @Override
-    public void onAnimationEnd(Animator animation) {
-        Iterator<Map.Entry<View, Integer>> itr = mViewsToLayerTypeMap.entrySet().iterator();
-        while (itr.hasNext()) {
-            Map.Entry<View, Integer> entry = itr.next();
-            entry.getKey().setLayerType(entry.getValue(), null);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 826a20e..6819386 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -45,12 +45,14 @@
     private final long mDuration;
 
     protected final AnimatorSet mAnim;
+    private AnimatorSet mOriginalTarget;
 
     protected float mCurrentFraction;
     private Runnable mEndAction;
 
     protected AnimatorPlaybackController(AnimatorSet anim, long duration) {
         mAnim = anim;
+        mOriginalTarget = mAnim;
         mDuration = duration;
 
         mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
@@ -63,6 +65,25 @@
         return mAnim;
     }
 
+    public void setOriginalTarget(AnimatorSet anim) {
+        mOriginalTarget = anim;
+    }
+
+    public AnimatorSet getOriginalTarget() {
+        return mOriginalTarget;
+    }
+
+    public long getDuration() {
+        return mDuration;
+    }
+
+    public AnimatorPlaybackController cloneFor(AnimatorSet anim) {
+        AnimatorPlaybackController controller = AnimatorPlaybackController.wrap(anim, mDuration);
+        controller.setOriginalTarget(mOriginalTarget);
+        controller.setPlayFraction(mCurrentFraction);
+        return controller;
+    }
+
     /**
      * Starts playing the animation forward from current position.
      */
@@ -211,6 +232,6 @@
     }
 
     private static <T> List<T> nonNullList(ArrayList<T> list) {
-        return list == null ? Collections.<T>emptyList() : list;
+        return list == null ? Collections.emptyList() : list;
     }
 }
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
new file mode 100644
index 0000000..63e67ff
--- /dev/null
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -0,0 +1,46 @@
+/*
+ * 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.anim;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+
+import com.android.launcher3.LauncherAnimUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class for building animator set
+ */
+public class AnimatorSetBuilder {
+
+    protected final ArrayList<Animator> mAnims = new ArrayList<>();
+
+    /**
+     * Associates a tag with all the animations added after this call.
+     */
+    public void startTag(Object obj) { }
+
+    public void play(Animator anim) {
+        mAnims.add(anim);
+    }
+
+    public AnimatorSet build() {
+        AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+        anim.playTogether(mAnims);
+        return anim;
+    }
+}
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
new file mode 100644
index 0000000..0c78381
--- /dev/null
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -0,0 +1,47 @@
+/*
+ * 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.compat;
+
+import android.content.Context;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+public class AccessibilityManagerCompat {
+
+    public static boolean isAccessibilityEnabled(Context context) {
+        return getManager(context).isEnabled();
+    }
+
+    public static boolean isObservedEventType(Context context, int eventType) {
+        // TODO: Use new API once available
+        return isAccessibilityEnabled(context);
+    }
+
+    public static void sendCustomAccessibilityEvent(View target, int type, String text) {
+        if (isObservedEventType(target.getContext(), type)) {
+            AccessibilityEvent event = AccessibilityEvent.obtain(type);
+            target.onInitializeAccessibilityEvent(event);
+            event.getText().add(text);
+            getManager(target.getContext()).sendAccessibilityEvent(event);
+        }
+    }
+
+    private static AccessibilityManager getManager(Context context) {
+        return (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+    }
+}
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 7cf3da0..0d92d45 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -60,7 +60,7 @@
     public static final boolean GO_DISABLE_WIDGETS = false;
 
     // When enabled shows a work profile tab in all apps
-    public static final boolean ALL_APPS_TABS_ENABLED = false;
+    public static final boolean ALL_APPS_TABS_ENABLED = true;
     // When enabled prediction row is rendered as it's own custom view
-    public static final boolean ALL_APPS_PREDICTION_ROW_VIEW = false;
+    public static final boolean ALL_APPS_PREDICTION_ROW_VIEW = true;
 }
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 9f9822c..27d6b89 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.dragndrop;
 
+import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.TimeInterpolator;
@@ -45,7 +47,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
@@ -90,9 +91,6 @@
     private final ViewGroupFocusHelper mFocusIndicatorHelper;
     private final PageCutOutScrimDrawable mPageCutOutScrim;
 
-    // Handles all apps pull up interaction
-    private AllAppsTransitionController mAllAppsController;
-
     protected TouchController[] mControllers;
     private TouchController mActiveController;
     /**
@@ -113,11 +111,9 @@
         mPageCutOutScrim.setCallback(this);
     }
 
-    public void setup(Launcher launcher, DragController dragController,
-            AllAppsTransitionController allAppsTransitionController) {
+    public void setup(Launcher launcher, DragController dragController) {
         mLauncher = launcher;
         mDragController = dragController;
-        mAllAppsController = allAppsTransitionController;
         mControllers = UiFactory.createTouchControllers(mLauncher);
     }
 
@@ -156,12 +152,7 @@
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         int action = ev.getAction();
 
-        if (action == MotionEvent.ACTION_DOWN) {
-            // Cancel discovery bounce animation when a user start interacting on anywhere on
-            // dray layer even if mAllAppsController is NOT the active controller.
-            // TODO: handle other input other than touch
-            mAllAppsController.cancelDiscoveryAnimation();
-        } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
             if (mTouchCompleteListener != null) {
                 mTouchCompleteListener.onTouchComplete();
             }
@@ -233,7 +224,7 @@
 
     private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
         int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
-        Utilities.sendCustomAccessibilityEvent(
+        sendCustomAccessibilityEvent(
                 this, AccessibilityEvent.TYPE_VIEW_FOCUSED, getContext().getString(stringId));
     }
 
@@ -800,6 +791,6 @@
     }
 
     public interface TouchCompleteListener {
-        public void onTouchComplete();
+        void onTouchComplete();
     }
 }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 2168001..b78e470 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -346,7 +347,7 @@
 
         mFolderName.setHint(sDefaultFolderName.contentEquals(newTitle) ? sHintText : null);
 
-        Utilities.sendCustomAccessibilityEvent(
+        sendCustomAccessibilityEvent(
                 this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
                 getContext().getString(R.string.folder_renamed, newTitle));
 
@@ -540,7 +541,7 @@
                 mFolderIcon.setBackgroundVisible(false);
                 mFolderIcon.drawLeaveBehindIfExists();
 
-                Utilities.sendCustomAccessibilityEvent(
+                sendCustomAccessibilityEvent(
                         Folder.this,
                         AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
                         mContent.getAccessibilityDescription());
@@ -654,7 +655,7 @@
             }
             @Override
             public void onAnimationStart(Animator animation) {
-                Utilities.sendCustomAccessibilityEvent(
+                sendCustomAccessibilityEvent(
                         Folder.this,
                         AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
                         getContext().getString(R.string.folder_closed));
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 3dc58a1..a166dff 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -85,6 +85,7 @@
 import java.util.Map;
 import java.util.Set;
 
+import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 import static com.android.launcher3.notification.NotificationMainView.NOTIFICATION_ITEM_INFO;
 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
@@ -573,7 +574,7 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 mOpenCloseAnimator = null;
-                Utilities.sendCustomAccessibilityEvent(
+                sendCustomAccessibilityEvent(
                         PopupContainerWithArrow.this,
                         AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
                         getContext().getString(R.string.action_deep_shortcut));
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 1d90a08..995cdaa 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -35,7 +35,8 @@
 public class SpringLoadedState extends LauncherState {
 
     private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE |
-            FLAG_DISABLE_ACCESSIBILITY | FLAG_DO_NOT_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED;
+            FLAG_DISABLE_ACCESSIBILITY | FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED |
+            FLAG_DISABLE_PAGE_CLIPPING;
 
     // Determines how long to wait after a rotation before restoring the screen orientation to
     // match the sensor state.
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
index 351f88d..ff5f64c 100644
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ b/src/com/android/launcher3/touch/SwipeDetector.java
@@ -336,7 +336,7 @@
     /**
      * Returns the linear interpolation between two values
      */
-    private static float interpolate(float from, float to, float alpha) {
+    public static float interpolate(float from, float to, float alpha) {
         return (1.0f - alpha) * from + alpha * to;
     }
 
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 8f20a8d..fc121d3 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -141,10 +141,11 @@
     }
 
     public void setRecyclerView(BaseRecyclerView rv, TextView popupView) {
-        mRv = rv;
-        if (mOnScrollListener != null) {
+        if (mRv != null && mOnScrollListener != null) {
             mRv.removeOnScrollListener(mOnScrollListener);
         }
+        mRv = rv;
+
         mRv.addOnScrollListener(mOnScrollListener = new RecyclerView.OnScrollListener() {
             @Override
             public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
@@ -321,7 +322,7 @@
      * Returns whether the specified point is inside the thumb bounds.
      */
     private boolean isNearThumb(int x, int y) {
-        int offset = y - mRv.getScrollBarTop() - mThumbOffsetY;
+        int offset = y - mThumbOffsetY;
 
         return x >= 0 && x < getWidth() && offset >= 0 && offset <= mThumbHeight;
     }
diff --git a/src/com/android/launcher3/states/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
similarity index 76%
copy from src/com/android/launcher3/states/AllAppsState.java
copy to src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
index ed3023a..bd5ddfe 100644
--- a/src/com/android/launcher3/states/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
@@ -13,9 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.states;
+package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
+import static com.android.launcher3.allapps.DiscoveryBounce.APPS_VIEW_SHOWN;
 
 import android.view.View;
 
@@ -23,6 +24,7 @@
 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;
 
 /**
@@ -30,8 +32,6 @@
  */
 public class AllAppsState extends LauncherState {
 
-    public static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown";
-
     private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY;
 
     public AllAppsState(int id) {
@@ -57,4 +57,16 @@
     public View getFinalFocus(Launcher launcher) {
         return launcher.getAppsView();
     }
+
+    @Override
+    public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
+        return new float[] { 1f,
+                -launcher.getAllAppsController().getShiftRange()
+                        * AllAppsTransitionController.PARALLAX_COEFFICIENT};
+    }
+
+    @Override
+    public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
+        return (i) -> 0;
+    }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
index 3ce1014..b23927b 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.WorkspaceStateTransitionAnimation.NO_ANIM_PROPERTY_SETTER;
 
-import android.animation.AnimatorSet;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -37,7 +36,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceStateTransitionAnimation.AnimatedPropertySetter;
 import com.android.launcher3.WorkspaceStateTransitionAnimation.PropertySetter;
-import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.widget.WidgetsFullSheet;
@@ -168,9 +167,9 @@
     }
 
     @Override
-    public void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
-            AnimatorSet anim, AnimationConfig config) {
-        setState(toState, new AnimatedPropertySetter(config.duration, layerViews, anim));
+    public void setStateWithAnimation(LauncherState toState,
+            AnimatorSetBuilder builder, AnimationConfig config) {
+        setState(toState, new AnimatedPropertySetter(config.duration, builder));
     }
 
     private void setState(LauncherState state, PropertySetter setter) {
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
index dcf7453..bcbe61c 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-import static com.android.launcher3.Utilities.isAccessibilityEnabled;
+import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 
 import android.graphics.Rect;
 import android.view.View;
@@ -36,7 +36,8 @@
     // 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;
+    private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE |
+            FLAG_DISABLE_PAGE_CLIPPING;
 
     public OverviewState(int id) {
         super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, 1f, STATE_FLAGS);
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java b/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java
index 40bf057..a7c8cee 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java
@@ -19,7 +19,7 @@
 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.Utilities.isAccessibilityEnabled;
+import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;