Merge "Turn on feature flags to eanble all apps personal/work tabs." into ub-launcher3-master
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/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 1176034..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,8 @@
  */
 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) {
         super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, 1f, STATE_FLAGS);
@@ -37,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..b59e4ee 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,39 +15,91 @@
  */
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.WorkspaceStateTransitionAnimation.NO_ANIM_PROPERTY_SETTER;
-
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
+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.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));
+        ObjectAnimator progressAnim =
+                mTransitionProgress.animateToValue(toState == LauncherState.OVERVIEW ? 1 : 0);
+        progressAnim.setDuration(config.duration);
+        progressAnim.setInterpolator(Interpolators.LINEAR);
+        anim.play(progressAnim);
+
+        ObjectAnimator visibilityAnim = animateVisibility(toState == LauncherState.OVERVIEW);
+        visibilityAnim.setDuration(config.duration);
+        visibilityAnim.setInterpolator(Interpolators.LINEAR);
+        anim.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/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 ba88f99..c0fd2cf 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -27,6 +27,8 @@
 import com.android.launcher3.Launcher;
 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;
@@ -48,6 +50,11 @@
     /** A circular curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
     private static final TimeInterpolator CURVE_INTERPOLATOR
         = x -> (float) (1 - Math.sqrt(1 - Math.pow(x, 2)));
+    /**
+     * The alpha of a black scrim on a page in the carousel as it leaves the screen.
+     * In the resting position of the carousel, the adjacent pages have about half this scrim.
+     */
+    private static final float MAX_PAGE_SCRIM_ALPHA = 0.8f;
 
     private boolean mOverviewStateEnabled;
     private boolean mTaskStackListenerRegistered;
@@ -65,6 +72,8 @@
         }
     };
 
+    private RecentsViewStateController mStateController;
+
     public RecentsView(Context context) {
         this(context, null);
     }
@@ -106,6 +115,14 @@
         updateTaskStackListenerState();
     }
 
+    public void setStateController(RecentsViewStateController stateController) {
+        mStateController = stateController;
+    }
+
+    public RecentsViewStateController getStateController() {
+        return mStateController;
+    }
+
     public void setOverviewStateEnabled(boolean enabled) {
         mOverviewStateEnabled = enabled;
         updateTaskStackListenerState();
@@ -166,7 +183,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);
@@ -180,9 +197,22 @@
         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 scrollTo(int x, int y) {
-        super.scrollTo(x, y);
+    public void computeScroll() {
+        super.computeScroll();
         updateCurveProperties();
     }
 
@@ -212,6 +242,10 @@
             // Make sure the biggest card (i.e. the one in front) shows on top of the adjacent ones.
             page.setTranslationZ(scale);
             page.setTranslationX((screenCenter - pageCenter) * curveInterpolation * CURVE_FACTOR);
+            if (page instanceof TaskView) {
+                TaskThumbnailView thumbnail = ((TaskView) page).getThumbnail();
+                thumbnail.setDimAlpha(1 - curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
+            }
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
index 029afd6..ac9a778 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -62,6 +62,7 @@
 
     @Override
     protected void onFinishInflate() {
+        super.onFinishInflate();
         mSnapshotView = findViewById(R.id.snapshot);
         mIconView = findViewById(R.id.icon);
     }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index eacc393..7b220d8 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -15,8 +15,15 @@
  */
 package com.android.quickstep;
 
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
+import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.INVALID_POINTER_ID;
 
+import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
 import android.app.Service;
@@ -29,6 +36,7 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -37,9 +45,12 @@
 import android.view.Display;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
+import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
 
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.util.TraceHelper;
@@ -51,9 +62,12 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.BackgroundExecutor;
 
+import java.util.function.Consumer;
+
 /**
  * Service connected by system-UI for handling touch interaction.
  */
+@TargetApi(Build.VERSION_CODES.O)
 public class TouchInteractionService extends Service {
 
     private static final String TAG = "TouchInteractionService";
@@ -73,6 +87,10 @@
         }
     };
 
+    private final Consumer<MotionEvent> mOtherActivityTouchConsumer
+            = this::handleTouchDownOnOtherActivity;
+    private final Consumer<MotionEvent> mNoOpTouchConsumer = (ev) -> {};
+
     private ActivityManagerWrapper mAM;
     private RunningTaskInfo mRunningTask;
     private Intent mHomeIntent;
@@ -80,8 +98,6 @@
     private MotionEventQueue mEventQueue;
     private MainThreadExecutor mMainThreadExecutor;
 
-    private int mDisplayRotation;
-    private final Point mDisplaySize = new Point();
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
     private int mActivePointerId = INVALID_POINTER_ID;
@@ -91,6 +107,7 @@
     private NavBarSwipeInteractionHandler mInteractionHandler;
 
     private ISystemUiProxy mISystemUiProxy;
+    private Consumer<MotionEvent> mCurrentConsumer = mNoOpTouchConsumer;
 
     @Override
     public void onCreate() {
@@ -128,25 +145,31 @@
     }
 
     private void handleMotionEvent(MotionEvent ev) {
-        if (ev.getActionMasked() != MotionEvent.ACTION_DOWN && mVelocityTracker == null) {
+        if (ev.getActionMasked() == ACTION_DOWN) {
+            mRunningTask = mAM.getRunningTask();
+
+            if (mRunningTask == null) {
+                mCurrentConsumer = mNoOpTouchConsumer;
+            } else if (mRunningTask.topActivity.equals(mLauncher)) {
+                mCurrentConsumer = getLauncherConsumer();
+            } else {
+                mCurrentConsumer = mOtherActivityTouchConsumer;
+            }
+        }
+        mCurrentConsumer.accept(ev);
+    }
+
+    private void handleTouchDownOnOtherActivity(MotionEvent ev) {
+        if (ev.getActionMasked() != ACTION_DOWN && mVelocityTracker == null) {
             return;
         }
         switch (ev.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN: {
+            case ACTION_DOWN: {
                 TraceHelper.beginSection("TouchInt");
                 mActivePointerId = ev.getPointerId(0);
                 mDownPos.set(ev.getX(), ev.getY());
                 mLastPos.set(mDownPos);
                 mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
-                Display display = getSystemService(WindowManager.class).getDefaultDisplay();
-                display.getRealSize(mDisplaySize);
-                mDisplayRotation = display.getRotation();
-
-                mRunningTask = mAM.getRunningTask();
-                if (mRunningTask == null || mRunningTask.topActivity.equals(mLauncher)) {
-                    // TODO: We could drive all-apps in this case. For now just ignore swipe.
-                    break;
-                }
 
                 if (mVelocityTracker == null) {
                     mVelocityTracker = VelocityTracker.obtain();
@@ -160,7 +183,7 @@
                 }
                 break;
             }
-            case MotionEvent.ACTION_POINTER_UP: {
+            case ACTION_POINTER_UP: {
                 int ptrIdx = ev.getActionIndex();
                 int ptrId = ev.getPointerId(ptrIdx);
                 if (ptrId == mActivePointerId) {
@@ -174,7 +197,7 @@
                 }
                 break;
             }
-            case MotionEvent.ACTION_MOVE: {
+            case ACTION_MOVE: {
                 int pointerIndex = ev.findPointerIndex(mActivePointerId);
                 if (pointerIndex == INVALID_POINTER_ID) {
                     break;
@@ -194,17 +217,19 @@
                 }
                 break;
             }
-            case MotionEvent.ACTION_CANCEL:
+            case ACTION_CANCEL:
                 // TODO: Should be different than ACTION_UP
-            case MotionEvent.ACTION_UP: {
+            case ACTION_UP: {
                 TraceHelper.endSection("TouchInt");
 
                 endInteraction();
+                mCurrentConsumer = mNoOpTouchConsumer;
                 break;
             }
         }
     }
 
+
     private void startTouchTracking() {
         // Create the shared handler
         final NavBarSwipeInteractionHandler handler =
@@ -262,9 +287,12 @@
 
         TraceHelper.beginSection("TaskSnapshot");
         // TODO: We are using some hardcoded layers for now, to best approximate the activity layers
+        Point displaySize = new Point();
+        Display display = getSystemService(WindowManager.class).getDefaultDisplay();
+        display.getRealSize(displaySize);
         try {
-            return mISystemUiProxy.screenshot(new Rect(), mDisplaySize.x, mDisplaySize.y, 0, 100000,
-                    false, mDisplayRotation).toBitmap();
+            return mISystemUiProxy.screenshot(new Rect(), displaySize.x, displaySize.y, 0, 100000,
+                    false, display.getRotation()).toBitmap();
         } catch (RemoteException e) {
             Log.e(TAG, "Error capturing snapshot", e);
             return null;
@@ -272,4 +300,76 @@
             TraceHelper.endSection("TaskSnapshot");
         }
     }
+
+    private Consumer<MotionEvent> getLauncherConsumer() {
+
+        Launcher launcher = (Launcher) LauncherAppState.getInstance(this).getModel().getCallback();
+        if (launcher == null) {
+            return mNoOpTouchConsumer;
+        }
+
+        View target = launcher.getDragLayer();
+        if (!target.getWindowId().isFocused()) {
+            return mNoOpTouchConsumer;
+        }
+        return new LauncherTouchConsumer(target);
+    }
+
+    private class LauncherTouchConsumer implements Consumer<MotionEvent> {
+
+        private final View mTarget;
+        private final int[] mLocationOnScreen = new int[2];
+
+        private boolean mTrackingStarted = false;
+
+        LauncherTouchConsumer(View target) {
+            mTarget = target;
+        }
+
+        @Override
+        public void accept(MotionEvent ev) {
+            int action = ev.getActionMasked();
+            if (action == ACTION_DOWN) {
+                mTrackingStarted = false;
+                mDownPos.set(ev.getX(), ev.getY());
+                mTouchSlop = ViewConfiguration.get(mTarget.getContext()).getScaledTouchSlop();
+            } else if (!mTrackingStarted) {
+                switch (action) {
+                    case ACTION_POINTER_UP:
+                    case ACTION_POINTER_DOWN:
+                        if (!mTrackingStarted) {
+                            mCurrentConsumer = mNoOpTouchConsumer;
+                        }
+                        break;
+                    case ACTION_MOVE: {
+                        float displacement = ev.getY() - mDownPos.y;
+                        if (Math.abs(displacement) >= mTouchSlop) {
+                            mTrackingStarted = true;
+                            mTarget.getLocationOnScreen(mLocationOnScreen);
+
+                            // Send a down event only when mTouchSlop is crossed.
+                            MotionEvent down = MotionEvent.obtain(ev);
+                            down.setAction(ACTION_DOWN);
+                            sendEvent(down);
+                            down.recycle();
+                        }
+                    }
+                }
+            }
+
+            if (mTrackingStarted) {
+                sendEvent(ev);
+            }
+
+            if (action == ACTION_UP || action == ACTION_CANCEL) {
+                mCurrentConsumer = mNoOpTouchConsumer;
+            }
+        }
+
+        private void sendEvent(MotionEvent ev) {
+            ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
+            mTarget.dispatchTouchEvent(ev);
+            ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
+        }
+    }
 }
diff --git a/res/drawable-hdpi/work_tab_user_education.png b/res/drawable-hdpi/work_tab_user_education.png
new file mode 100644
index 0000000..1879dfb
--- /dev/null
+++ b/res/drawable-hdpi/work_tab_user_education.png
Binary files differ
diff --git a/res/drawable-mdpi/work_tab_user_education.png b/res/drawable-mdpi/work_tab_user_education.png
new file mode 100644
index 0000000..65c7e63
--- /dev/null
+++ b/res/drawable-mdpi/work_tab_user_education.png
Binary files differ
diff --git a/res/drawable-xhdpi/work_tab_user_education.png b/res/drawable-xhdpi/work_tab_user_education.png
new file mode 100644
index 0000000..59df7a8
--- /dev/null
+++ b/res/drawable-xhdpi/work_tab_user_education.png
Binary files differ
diff --git a/res/drawable-xxhdpi/work_tab_user_education.png b/res/drawable-xxhdpi/work_tab_user_education.png
new file mode 100644
index 0000000..3c6aa20
--- /dev/null
+++ b/res/drawable-xxhdpi/work_tab_user_education.png
Binary files differ
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..be509ed 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"
@@ -48,12 +48,16 @@
             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
             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" >
             <Button
@@ -73,7 +77,7 @@
                 android:textColor="@color/all_apps_tab_text"
                 android:background="?android:attr/selectableItemBackground"/>
         </com.android.launcher3.views.SlidingTabStrip>
-    </RelativeLayout>
+    </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/work_tab_bottom_user_education_view.xml b/res/layout/work_tab_bottom_user_education_view.xml
new file mode 100644
index 0000000..2a4ba5d
--- /dev/null
+++ b/res/layout/work_tab_bottom_user_education_view.xml
@@ -0,0 +1,57 @@
+<?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.views.BottomUserEducationView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="bottom"
+    android:background="?android:attr/colorAccent"
+    android:elevation="2dp"
+    android:orientation="horizontal"
+    android:paddingLeft="20dp"
+    android:paddingRight="20dp">
+
+    <ImageView
+        android:layout_width="134dp"
+        android:layout_height="134dp"
+        android:layout_gravity="center_vertical"
+        android:src="@drawable/work_tab_user_education"/>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingBottom="12dp"
+        android:paddingStart="24dp"
+        android:paddingTop="12dp">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="roboto-medium"
+            android:text="@string/bottom_work_tab_user_education_title"
+            android:textColor="@android:color/white"
+            android:textSize="20sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/bottom_work_tab_user_education_body"
+            android:textColor="@android:color/white"
+            android:textSize="14sp"/>
+    </LinearLayout>
+
+</com.android.launcher3.views.BottomUserEducationView>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index e7f45c2..e22c7c2 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/res/values/strings.xml b/res/values/strings.xml
index cbba9a2..eca64c8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -326,5 +326,9 @@
 
     <!-- Label of the work mode toggle -->
     <string name="work_profile_toggle_label">Work profile</string>
+    <!-- Title in bottom user education view in work tab -->
+    <string name="bottom_work_tab_user_education_title">Find work apps here</string>
+    <!-- Body text in bottom user education view in work tab -->
+    <string name="bottom_work_tab_user_education_body">Each work app has an orange badge, which means it\'s kept secure by your organization. Work apps can be moved to your Home Screen for easier access.</string>
 
 </resources>
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 26024e5..da464c0 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -42,7 +42,8 @@
             TYPE_WIDGETS_BOTTOM_SHEET,
             TYPE_WIDGET_RESIZE_FRAME,
             TYPE_WIDGETS_FULL_SHEET,
-            TYPE_QUICKSTEP_PREVIEW
+            TYPE_QUICKSTEP_PREVIEW,
+            TYPE_ON_BOARD_POPUP
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface FloatingViewType {}
@@ -52,10 +53,15 @@
     public static final int TYPE_WIDGET_RESIZE_FRAME = 1 << 3;
     public static final int TYPE_WIDGETS_FULL_SHEET = 1 << 4;
     public static final int TYPE_QUICKSTEP_PREVIEW = 1 << 5;
+    public static final int TYPE_ON_BOARD_POPUP = 1 << 6;
 
     public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
-            | TYPE_QUICKSTEP_PREVIEW;
+            | 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;
 
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/Launcher.java b/src/com/android/launcher3/Launcher.java
index b7986da..a9b1619 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -60,7 +60,6 @@
 import android.content.pm.PackageManager;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
@@ -99,10 +98,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;
@@ -121,7 +120,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;
@@ -848,9 +846,7 @@
         // Refresh shortcuts if the permission changed.
         mModel.refreshShortcutsIfRequired();
 
-        if (shouldShowDiscoveryBounce()) {
-            mAllAppsController.showDiscoveryBounce();
-        }
+        DiscoveryBounce.showIfNeeded(this);
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onResume();
         }
@@ -1007,7 +1003,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 */);
         }
 
@@ -1040,7 +1036,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);
@@ -1969,7 +1965,8 @@
         final ShortcutInfo shortcut = (ShortcutInfo) tag;
 
         if (shortcut.isDisabled()) {
-            if ((shortcut.runtimeStatusFlags &
+            final int disabledFlags = shortcut.runtimeStatusFlags & ShortcutInfo.FLAG_DISABLED_MASK;
+            if ((disabledFlags &
                     ~FLAG_DISABLED_SUSPENDED &
                     ~FLAG_DISABLED_QUIET_USER) == 0) {
                 // If the app is only disabled because of the above flags, launch activity anyway.
@@ -2428,7 +2425,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);
 
@@ -3069,12 +3066,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/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index d6cd8a3..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,12 +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);
+            0, 1f, FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED);
 
     public static final LauncherState ALL_APPS = new AllAppsState(1);
 
@@ -60,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.
@@ -88,6 +92,17 @@
      */
     public final float verticalProgress;
 
+    /**
+     * True if the state allows workspace icons to be dragged.
+     */
+    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;
@@ -98,7 +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;
 
@@ -128,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..2cad95e 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -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) {
@@ -217,7 +217,7 @@
             @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 +237,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();
     }
 
     /**
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 9f6efb3..b23568e 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -593,6 +593,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/Workspace.java b/src/com/android/launcher3/Workspace.java
index 0db5a16..3d59bad 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -193,10 +193,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 +432,6 @@
         mCurrentPage = DEFAULT_PAGE;
         DeviceProfile grid = mLauncher.getDeviceProfile();
         setWillNotDraw(false);
-        setClipChildren(false);
         setClipToPadding(false);
 
         setupLayoutTransition();
@@ -1191,33 +1186,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 +1208,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);
             }
         }
@@ -1407,7 +1390,7 @@
 
     /** Returns whether a drag should be allowed to be started from the current workspace state. */
     public boolean workspaceIconsCanBeDragged() {
-        return mLauncher.isInState(NORMAL) || mLauncher.isInState(SPRING_LOADED);
+        return mLauncher.getStateManager().getState().workspaceIconsCanBeDragged;
     }
 
     private void updateChildrenLayersEnabled() {
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 8edec40..edf5ada 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -26,11 +26,11 @@
 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.Interpolators;
@@ -97,18 +97,13 @@
     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) {
@@ -134,10 +129,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 +146,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 {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index a6c1e6e..23a694c 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -27,6 +27,7 @@
 import android.support.v7.widget.RecyclerView;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -59,6 +60,7 @@
 import com.android.launcher3.util.ItemInfoMatcher;
 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;
@@ -83,8 +85,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;
@@ -95,6 +96,7 @@
     private TransformingTouchDelegate mTouchDelegate;
     private boolean mUsingTabs;
     private boolean mHasPredictions = false;
+    private boolean mSearchModeWhileUsingTabs = false;
 
     private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
 
@@ -231,8 +233,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());
         }
     }
 
@@ -259,9 +261,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());
     }
@@ -283,8 +282,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();
@@ -306,7 +305,6 @@
         });
 
         mHeader = findViewById(R.id.all_apps_header);
-        mFloatingHeaderHandler = new FloatingHeaderHandler(mHeader);
         rebindAdapters(mUsingTabs);
 
         mSearchContainer = findViewById(R.id.search_container_all_apps);
@@ -444,7 +442,6 @@
             if (FeatureFlags.ALL_APPS_PREDICTION_ROW_VIEW) {
                 setupHeader();
             } else {
-                mFloatingHeaderHandler = null;
                 mHeader.setVisibility(View.GONE);
             }
         }
@@ -490,35 +487,29 @@
         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();
                 }
+                if (pos == AdapterHolder.WORK) {
+                    BottomUserEducationView.showIfNeeded(mLauncher);
+                }
             }
 
             @Override
             public void onPageScrollStateChanged(int state) {
             }
         });
+        mAH[AdapterHolder.MAIN].recyclerView.bindFastScrollbar();
 
         findViewById(R.id.tab_personal)
                 .setOnClickListener((View view) -> mViewPager.setCurrentItem(0));
@@ -527,14 +518,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();
+            }
         }
     }
 
@@ -550,24 +543,26 @@
         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);
-        int contentHeight = mHasPredictions ? mLauncher.getDeviceProfile().allAppsCellHeightPx : 0;
-        if (mHasPredictions && !mUsingTabs) {
+
+        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,
+        AllAppsRecyclerView mainRV = mAH[AdapterHolder.MAIN].recyclerView;
+        AllAppsRecyclerView workRV = mAH[AdapterHolder.WORK].recyclerView;
+        mHeader.setup(mainRV, workRV, contentHeight);
+        mHeader.getPredictionRow().setup(mAH[AdapterHolder.MAIN].adapter,
                 mComponentToAppMap, mNumPredictedAppsPerRow);
 
         int padding = contentHeight;
@@ -584,6 +579,14 @@
         for (int i = 0; i < mAH.length; i++) {
             mAH[i].adapter.setLastSearchQuery(query);
         }
+        boolean hasQuery = !TextUtils.isEmpty(query);
+        if (mUsingTabs && hasQuery) {
+            mSearchModeWhileUsingTabs = true;
+            rebindAdapters(false); // hide tabs
+        } else if (mSearchModeWhileUsingTabs && !hasQuery) {
+            mSearchModeWhileUsingTabs = false;
+            rebindAdapters(true); // show tabs
+        }
     }
 
     public void onSearchResultsChanged() {
@@ -623,12 +626,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;
@@ -678,8 +685,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);
             }
         }
@@ -691,8 +698,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..7ce032f 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -4,7 +4,6 @@
 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;
@@ -54,10 +53,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 +75,6 @@
 
     private static final float DEFAULT_SHIFT_RANGE = 10;
 
-    private boolean mIsTranslateWithoutWorkspace = false;
-    private Animator mDiscoBounceAnimation;
     private GradientView mGradientView;
 
     public AllAppsTransitionController(Launcher l) {
@@ -134,7 +130,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 +145,6 @@
                     hotseatAlpha);
         }
 
-        if (mIsTranslateWithoutWorkspace) {
-            return;
-        }
-        mWorkspace.setWorkspaceYTranslationAndAlpha(
-                PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent), workspaceAlpha);
-
         updateLightStatusBar(shiftCurrent);
     }
 
@@ -191,7 +180,13 @@
                 this, PROGRESS, mProgress, toState.verticalProgress);
         anim.setDuration(config.duration);
         anim.setInterpolator(interpolator);
-        anim.addListener(new AnimationSuccessListener() {
+        anim.addListener(getProgressAnimatorListener());
+
+        animationOut.play(anim);
+    }
+
+    public AnimatorListenerAdapter getProgressAnimatorListener() {
+        return new AnimationSuccessListener() {
             @Override
             public void onAnimationSuccess(Animator animator) {
                 onProgressAnimationEnd();
@@ -201,50 +196,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/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 12715cb..2b9cba3 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -301,7 +301,7 @@
         if (mSearchResults != f) {
             boolean same = mSearchResults != null && mSearchResults.equals(f);
             mSearchResults = f;
-            updateAdapterItems();
+            onAppsUpdated();
             return !same;
         }
         return false;
@@ -403,7 +403,7 @@
         mApps.clear();
 
         for (AppInfo app : mComponentToAppMap.values()) {
-            if (mItemFilter == null || mItemFilter.matches(app, null)) {
+            if (mItemFilter == null || mItemFilter.matches(app, null) || hasFilter()) {
                 mApps.add(app);
             }
         }
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..4f1a0d5
--- /dev/null
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -0,0 +1,273 @@
+/*
+ * 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.R;
+
+public class FloatingHeaderView extends RelativeLayout implements
+        ValueAnimator.AnimatorUpdateListener {
+
+    private static final boolean SHOW_PREDICTIONS_ONLY_ON_TOP = true;
+
+    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) {
+            if (SHOW_PREDICTIONS_ONLY_ON_TOP) {
+                return;
+            }
+            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);
+            }
+        }
+
+        @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
+                    ? mMainRV.getCurrentScrollY()
+                    : mWorkRV.getCurrentScrollY());
+            moved(current);
+            apply();
+        }
+    };
+
+    private PredictionRowView mPredictionRow;
+    private ViewGroup mTabLayout;
+    private View mDivider;
+    private AllAppsRecyclerView mMainRV;
+    private AllAppsRecyclerView mWorkRV;
+    private ViewGroup mParent;
+    private boolean mTopOnlyMode;
+    private boolean mHeaderHidden;
+    private int mMaxTranslation;
+    private int mSnappedScrolledY;
+    private int mTranslationY;
+    private int mMainScrolledY;
+    private int mWorkScrolledY;
+    private boolean mMainRVActive = true;
+    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(@NonNull AllAppsRecyclerView personalRV, @Nullable AllAppsRecyclerView workRV,
+                      int predictionRowHeight) {
+        mTopOnlyMode = workRV == null;
+        mTabLayout.setVisibility(mTopOnlyMode ? View.GONE : View.VISIBLE);
+        mPredictionRow.getLayoutParams().height = predictionRowHeight;
+        mMaxTranslation = predictionRowHeight;
+        mMainRV = setupRV(mMainRV, personalRV);
+        mWorkRV = setupRV(mWorkRV, workRV);
+        mParent = (ViewGroup) getRV().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, 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 PredictionRowView getPredictionRow() {
+        return mPredictionRow;
+    }
+
+    public ViewGroup getTabLayout() {
+        return mTabLayout;
+    }
+
+    public View getDivider() {
+        return mDivider;
+    }
+
+    public void reset() {
+        mMainScrolledY = 0;
+        mWorkScrolledY = 0;
+        setExpanded(true);
+    }
+
+    private boolean canSnapAt(int currentScrollY) {
+        boolean snapOnlyOnTop = SHOW_PREDICTIONS_ONLY_ON_TOP || mTopOnlyMode;
+        return !snapOnlyOnTop || 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);
+        }
+    }
+
+    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();
+    }
+
+    private AllAppsRecyclerView getRV() {
+        return mMainRVActive ? mMainRV : mWorkRV;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        calcOffset(mTempOffset);
+        ev.offsetLocation(mTempOffset.x, mTempOffset.y);
+        mForwardToRecyclerView = getRV().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 getRV().onTouchEvent(event);
+            } finally {
+                event.offsetLocation(-mTempOffset.x, -mTempOffset.y);
+            }
+        } else {
+            return super.onTouchEvent(event);
+        }
+    }
+
+    private void calcOffset(Point p) {
+        p.x = getLeft() - getRV().getLeft() - mParent.getLeft();
+        p.y = getTop() - getRV().getTop() - mParent.getTop();
+    }
+
+}
+
+
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 9f9822c..cf76e2e 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -45,7 +45,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 +89,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 +109,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 +150,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();
             }
@@ -800,6 +789,6 @@
     }
 
     public interface TouchCompleteListener {
-        public void onTouchComplete();
+        void onTouchComplete();
     }
 }
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 3864e3a..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_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/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
new file mode 100644
index 0000000..7c4529d
--- /dev/null
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -0,0 +1,182 @@
+/*
+ * 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.views;
+
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.touch.SwipeDetector;
+
+/**
+ * Extension of AbstractFloatingView with common methods for sliding in from bottom
+ */
+public abstract class AbstractSlideInView extends AbstractFloatingView
+        implements SwipeDetector.Listener {
+
+    protected static Property<AbstractSlideInView, Float> TRANSLATION_SHIFT =
+            new Property<AbstractSlideInView, Float>(Float.class, "translationShift") {
+
+                @Override
+                public Float get(AbstractSlideInView view) {
+                    return view.mTranslationShift;
+                }
+
+                @Override
+                public void set(AbstractSlideInView view, Float value) {
+                    view.setTranslationShift(value);
+                }
+            };
+    protected static final float TRANSLATION_SHIFT_CLOSED = 1f;
+    protected static final float TRANSLATION_SHIFT_OPENED = 0f;
+
+    protected final Launcher mLauncher;
+    protected final SwipeDetector mSwipeDetector;
+    protected final ObjectAnimator mOpenCloseAnimator;
+
+    protected View mContent;
+    protected Interpolator mScrollInterpolator;
+
+    // range [0, 1], 0=> completely open, 1=> completely closed
+    protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED;
+
+    protected boolean mNoIntercept;
+
+    public AbstractSlideInView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(context);
+
+        mScrollInterpolator = Interpolators.SCROLL_CUBIC;
+        mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL);
+
+        mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
+        mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mSwipeDetector.finishedScrolling();
+            }
+        });
+    }
+
+    protected void setTranslationShift(float translationShift) {
+        mTranslationShift = translationShift;
+        mContent.setTranslationY(mTranslationShift * mContent.getHeight());
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (mNoIntercept) {
+            return false;
+        }
+
+        int directionsToDetectScroll = mSwipeDetector.isIdleState() ?
+                SwipeDetector.DIRECTION_NEGATIVE : 0;
+        mSwipeDetector.setDetectableScrollConditions(
+                directionsToDetectScroll, false);
+        mSwipeDetector.onTouchEvent(ev);
+        return mSwipeDetector.isDraggingOrSettling()
+                || !mLauncher.getDragLayer().isEventOverView(mContent, ev);
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        mSwipeDetector.onTouchEvent(ev);
+        if (ev.getAction() == MotionEvent.ACTION_UP && mSwipeDetector.isIdleState()) {
+            // If we got ACTION_UP without ever starting swipe, close the panel.
+            if (!mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
+                close(true);
+            }
+        }
+        return true;
+    }
+
+    /* SwipeDetector.Listener */
+
+    @Override
+    public void onDragStart(boolean start) { }
+
+    @Override
+    public boolean onDrag(float displacement, float velocity) {
+        float range = mContent.getHeight();
+        displacement = Utilities.boundToRange(displacement, 0, range);
+        setTranslationShift(displacement / range);
+        return true;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        if ((fling && velocity > 0) || mTranslationShift > 0.5f) {
+            mScrollInterpolator = scrollInterpolatorForVelocity(velocity);
+            mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(
+                    velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift));
+            close(true);
+        } else {
+            mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(
+                    TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+            mOpenCloseAnimator.setDuration(
+                    SwipeDetector.calculateDuration(velocity, mTranslationShift))
+                    .setInterpolator(Interpolators.DEACCEL);
+            mOpenCloseAnimator.start();
+        }
+    }
+
+    protected void handleClose(boolean animate, long defaultDuration) {
+        if (mIsOpen && !animate) {
+            mOpenCloseAnimator.cancel();
+            setTranslationShift(TRANSLATION_SHIFT_CLOSED);
+            onCloseComplete();
+            return;
+        }
+        if (!mIsOpen || mOpenCloseAnimator.isRunning()) {
+            return;
+        }
+        mOpenCloseAnimator.setValues(
+                PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED));
+        mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                onCloseComplete();
+            }
+        });
+        if (mSwipeDetector.isIdleState()) {
+            mOpenCloseAnimator
+                    .setDuration(defaultDuration)
+                    .setInterpolator(Interpolators.ACCEL);
+        } else {
+            mOpenCloseAnimator.setInterpolator(mScrollInterpolator);
+        }
+        mOpenCloseAnimator.start();
+    }
+
+    protected void onCloseComplete() {
+        mIsOpen = false;
+        mLauncher.getDragLayer().removeView(this);
+    }
+}
diff --git a/src/com/android/launcher3/views/BottomUserEducationView.java b/src/com/android/launcher3/views/BottomUserEducationView.java
new file mode 100644
index 0000000..d79d0ce
--- /dev/null
+++ b/src/com/android/launcher3/views/BottomUserEducationView.java
@@ -0,0 +1,113 @@
+/*
+ * 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.views;
+
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.Interpolators;
+
+public class BottomUserEducationView extends AbstractSlideInView implements Insettable {
+
+    private static final String KEY_SHOWED_BOTTOM_USER_EDUCATION = "showed_bottom_user_education";
+
+    private static final int DEFAULT_CLOSE_DURATION = 200;
+
+    private final Rect mInsets = new Rect();
+
+    public BottomUserEducationView(Context context, AttributeSet attr) {
+        this(context, attr, 0);
+    }
+
+    public BottomUserEducationView(Context context, AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mContent = this;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        setTranslationShift(mTranslationShift);
+    }
+
+    @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;
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        // Extend behind left, right, and bottom insets.
+        int leftInset = insets.left - mInsets.left;
+        int rightInset = insets.right - mInsets.right;
+        int bottomInset = insets.bottom - mInsets.bottom;
+        mInsets.set(insets);
+        setPadding(getPaddingLeft() + leftInset, getPaddingTop(),
+                getPaddingRight() + rightInset, getPaddingBottom() + bottomInset);
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        handleClose(animate, DEFAULT_CLOSE_DURATION);
+        if (animate) {
+            // We animate only when the user is visible, which is a proxy for an explicit
+            // close action.
+            mLauncher.getSharedPrefs().edit()
+                    .putBoolean(KEY_SHOWED_BOTTOM_USER_EDUCATION, true).apply();
+        }
+    }
+
+    private void open(boolean animate) {
+        if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+            return;
+        }
+        mIsOpen = true;
+        if (animate) {
+            mOpenCloseAnimator.setValues(
+                    PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+            mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+            mOpenCloseAnimator.start();
+        } else {
+            setTranslationShift(TRANSLATION_SHIFT_OPENED);
+        }
+    }
+
+    public static void showIfNeeded(Launcher launcher) {
+        if (launcher.getSharedPrefs().getBoolean(KEY_SHOWED_BOTTOM_USER_EDUCATION, false)) {
+            return;
+        }
+
+        LayoutInflater layoutInflater = LayoutInflater.from(launcher);
+        BottomUserEducationView bottomUserEducationView =
+                (BottomUserEducationView) layoutInflater.inflate(
+                        R.layout.work_tab_bottom_user_education_view, launcher.getDragLayer(),
+                        false);
+        launcher.getDragLayer().addView(bottomUserEducationView);
+        bottomUserEducationView.open(true);
+    }
+}
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/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index e328759..fa82714 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -15,94 +15,43 @@
  */
 package com.android.launcher3.widget;
 
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.graphics.Point;
 import android.util.AttributeSet;
-import android.util.Property;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
-import android.view.animation.Interpolator;
 import android.widget.Toast;
 
-import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.graphics.GradientView;
-import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.AbstractSlideInView;
 
 /**
  * Base class for various widgets popup
  */
-abstract class BaseWidgetSheet extends AbstractFloatingView
-        implements OnClickListener, OnLongClickListener, DragSource, SwipeDetector.Listener {
+abstract class BaseWidgetSheet extends AbstractSlideInView
+        implements OnClickListener, OnLongClickListener, DragSource {
 
 
-    protected static Property<BaseWidgetSheet, Float> TRANSLATION_SHIFT =
-            new Property<BaseWidgetSheet, Float>(Float.class, "translationShift") {
-
-                @Override
-                public Float get(BaseWidgetSheet view) {
-                    return view.mTranslationShift;
-                }
-
-                @Override
-                public void set(BaseWidgetSheet view, Float value) {
-                    view.setTranslationShift(value);
-                }
-            };
-    protected static final float TRANSLATION_SHIFT_CLOSED = 1f;
-    protected static final float TRANSLATION_SHIFT_OPENED = 0f;
-
     /* Touch handling related member variables. */
     private Toast mWidgetInstructionToast;
 
-    protected final Launcher mLauncher;
-    protected final SwipeDetector mSwipeDetector;
-    protected final ObjectAnimator mOpenCloseAnimator;
-
-    protected View mContent;
     protected GradientView mGradientView;
-    protected Interpolator mScrollInterpolator;
-
-    // range [0, 1], 0=> completely open, 1=> completely closed
-    protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED;
-
-    protected boolean mNoIntercept;
 
     public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mLauncher = Launcher.getLauncher(context);
-
-        mScrollInterpolator = Interpolators.SCROLL_CUBIC;
-        mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL);
-
-        mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
-        mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mSwipeDetector.finishedScrolling();
-            }
-        });
     }
 
     @Override
@@ -130,9 +79,8 @@
     }
 
     protected void setTranslationShift(float translationShift) {
-        mTranslationShift = translationShift;
+        super.setTranslationShift(translationShift);
         mGradientView.setAlpha(1 - mTranslationShift);
-        mContent.setTranslationY(mTranslationShift * mContent.getHeight());
     }
 
     private boolean beginDraggingWidget(WidgetCell v) {
@@ -163,94 +111,8 @@
     public void onDropCompleted(View target, DragObject d, boolean success) { }
 
 
-    @Override
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_UP && !mNoIntercept) {
-            // If we got ACTION_UP without ever returning true on intercept,
-            // the user never started dragging the bottom sheet.
-            if (!mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
-                close(true);
-                return false;
-            }
-        }
-
-        if (mNoIntercept) {
-            return false;
-        }
-
-        int directionsToDetectScroll = mSwipeDetector.isIdleState() ?
-                SwipeDetector.DIRECTION_NEGATIVE : 0;
-        mSwipeDetector.setDetectableScrollConditions(
-                directionsToDetectScroll, false);
-        mSwipeDetector.onTouchEvent(ev);
-        return mSwipeDetector.isDraggingOrSettling();
-    }
-
-    @Override
-    public boolean onControllerTouchEvent(MotionEvent ev) {
-        return mSwipeDetector.onTouchEvent(ev);
-    }
-
-    /* SwipeDetector.Listener */
-
-    @Override
-    public void onDragStart(boolean start) { }
-
-    @Override
-    public boolean onDrag(float displacement, float velocity) {
-        float range = mContent.getHeight();
-        displacement = Utilities.boundToRange(displacement, 0, range);
-        setTranslationShift(displacement / range);
-        return true;
-    }
-
-    @Override
-    public void onDragEnd(float velocity, boolean fling) {
-        if ((fling && velocity > 0) || mTranslationShift > 0.5f) {
-            mScrollInterpolator = scrollInterpolatorForVelocity(velocity);
-            mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(
-                    velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift));
-            close(true);
-        } else {
-            mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(
-                    TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
-            mOpenCloseAnimator.setDuration(
-                    SwipeDetector.calculateDuration(velocity, mTranslationShift))
-                    .setInterpolator(Interpolators.DEACCEL);
-            mOpenCloseAnimator.start();
-        }
-    }
-
-    protected void handleClose(boolean animate, long defaultDuration) {
-        if (!mIsOpen || mOpenCloseAnimator.isRunning()) {
-            return;
-        }
-        if (animate) {
-            mOpenCloseAnimator.setValues(
-                    PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED));
-            mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    onCloseComplete();
-                }
-            });
-            if (mSwipeDetector.isIdleState()) {
-                mOpenCloseAnimator
-                        .setDuration(defaultDuration)
-                        .setInterpolator(Interpolators.ACCEL);
-            } else {
-                mOpenCloseAnimator.setInterpolator(mScrollInterpolator);
-            }
-            mOpenCloseAnimator.start();
-        } else {
-            setTranslationShift(TRANSLATION_SHIFT_CLOSED);
-            onCloseComplete();
-        }
-    }
-
     protected void onCloseComplete() {
-        mIsOpen = false;
-        mLauncher.getDragLayer().removeView(this);
+        super.onCloseComplete();
         mLauncher.getSystemUiController().updateUiState(
                 SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, 0);
     }
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/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
index dcf7453..2cd3731 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
@@ -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);