Merge "Added StatusBarNotificationsListener interface." into ub-launcher3-master
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 9069698..b3025ff 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 587261d..222a3f4 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -24,4 +24,7 @@
     <dimen name="quickstep_fling_min_velocity">250dp</dimen>
 
     <dimen name="workspace_overview_offset_x">-30dp</dimen>
+
+    <!-- TODO: This can be calculated using other resource values -->
+    <dimen name="all_apps_search_box_full_height">90dp</dimen>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index a4851ba..6395473 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -42,7 +42,7 @@
     };
 
     public AllAppsState(int id) {
-        super(id, ContainerType.ALLAPPS, ALL_APPS_TRANSITION_MS, 0f, STATE_FLAGS);
+        super(id, ContainerType.ALLAPPS, ALL_APPS_TRANSITION_MS, STATE_FLAGS);
     }
 
     @Override
@@ -61,6 +61,11 @@
     }
 
     @Override
+    public float getVerticalProgress(Launcher launcher) {
+        return 0f;
+    }
+
+    @Override
     public View getFinalFocus(Launcher launcher) {
         return launcher.getAppsView();
     }
@@ -75,4 +80,14 @@
     public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
         return PAGE_ALPHA_PROVIDER;
     }
+
+    @Override
+    public float getHoseatAlpha(Launcher launcher) {
+        return launcher.getDeviceProfile().isVerticalBarLayout() ? 0 : 1;
+    }
+
+    @Override
+    public LauncherState getHistoryForState(LauncherState previousState) {
+        return previousState == OVERVIEW ? OVERVIEW : NORMAL;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
new file mode 100644
index 0000000..c1b26d4
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.touch.SwipeDetector.DIRECTION_NEGATIVE;
+import static com.android.launcher3.touch.SwipeDetector.DIRECTION_POSITIVE;
+import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
+import static com.android.launcher3.touch.SwipeDetector.VERTICAL;
+import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
+
+import android.graphics.Rect;
+import android.view.MotionEvent;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.anim.SpringAnimationHandler;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.util.VerticalSwipeController;
+import com.android.quickstep.RecentsView;
+
+/**
+ * Extension of {@link VerticalSwipeController} to go from NORMAL to OVERVIEW.
+ */
+public class EdgeSwipeController extends VerticalSwipeController {
+
+    private final Rect mTempRect = new Rect();
+
+    public EdgeSwipeController(Launcher l) {
+        super(l, NORMAL, OVERVIEW, l.getDeviceProfile().isVerticalBarLayout()
+                ? HORIZONTAL : VERTICAL);
+    }
+
+    @Override
+    protected boolean shouldInterceptTouch(MotionEvent ev) {
+        return mLauncher.isInState(NORMAL) && (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
+    }
+
+    @Override
+    protected int getSwipeDirection(MotionEvent ev) {
+        return isTransitionFlipped() ? DIRECTION_NEGATIVE : DIRECTION_POSITIVE;
+    }
+
+    @Override
+    protected boolean isTransitionFlipped() {
+        if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+            Rect insets = mLauncher.getDragLayer().getInsets();
+            return insets.left > insets.right;
+        }
+        return false;
+    }
+
+    @Override
+    protected void onTransitionComplete(boolean wasFling, boolean stateChanged) {
+        // TODO: Log something
+    }
+
+    @Override
+    protected void initSprings() {
+        mSpringHandlers = new SpringAnimationHandler[0];
+    }
+
+    @Override
+    protected float getShiftRange() {
+        RecentsView.getPageRect(mLauncher, mTempRect);
+        DragLayer dl = mLauncher.getDragLayer();
+        Rect insets = dl.getInsets();
+
+        if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+            if (insets.left > insets.right) {
+                return insets.left + mTempRect.left;
+            } else {
+                return dl.getWidth() - mTempRect.right + insets.right;
+            }
+        } else {
+            return dl.getHeight() - mTempRect.bottom + insets.bottom;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index f1da817..2e5e75e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -16,10 +16,13 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 
+import android.content.Context;
 import android.graphics.Rect;
 import android.view.View;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
@@ -38,7 +41,7 @@
     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);
+        super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
     }
 
     @Override
@@ -59,11 +62,6 @@
     }
 
     @Override
-    public float getHoseatAlpha(Launcher launcher) {
-        return launcher.getDeviceProfile().isVerticalBarLayout() ? 0 : 1;
-    }
-
-    @Override
     public void onStateEnabled(Launcher launcher) {
         RecentsView rv = launcher.getOverviewPanel();
         rv.setOverviewStateEnabled(true);
@@ -76,27 +74,59 @@
     }
 
     @Override
+    public float getVerticalProgress(Launcher launcher) {
+        return getVerticalProgress(launcher.getDeviceProfile(), launcher);
+    }
+
+    @Override
     public View getFinalFocus(Launcher launcher) {
         return launcher.getOverviewPanel();
     }
 
+    public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
+        final int centerPage = launcher.getWorkspace().getNextPage();
+        return new PageAlphaProvider(ACCEL_2) {
+            @Override
+            public float getPageAlpha(int pageIndex) {
+                return  pageIndex != centerPage ? 0 : 1f;
+            }
+        };
+    }
+
     public static float[] getScaleAndTranslationForPageRect(Launcher launcher, float offsetX,
             Rect pageRect) {
         Workspace ws = launcher.getWorkspace();
         float childWidth = ws.getNormalChildWidth();
+        float childHeight = ws.getNormalChildHeight();
 
         Rect insets = launcher.getDragLayer().getInsets();
-        float scale = pageRect.width() / childWidth;
-
-        float translationX = offsetX / scale;
-        if (Utilities.isRtl(launcher.getResources())) {
-            translationX = -translationX;
-        }
+        float scale = Math.min(pageRect.width() / childWidth, pageRect.height() / childHeight);
 
         float halfHeight = ws.getHeight() / 2;
         float childTop = halfHeight - scale * (halfHeight - ws.getPaddingTop() - insets.top);
         float translationY = pageRect.top - childTop;
 
+        float halfWidth = ws.getWidth() / 2;
+        float translationX;
+        if (Utilities.isRtl(launcher.getResources())) {
+            float childRight = halfWidth + scale * (halfWidth - ws.getPaddingRight() - insets.right);
+            translationX = childRight - pageRect.right - offsetX / scale;
+        } else {
+            float childLeft = halfWidth - scale * (halfWidth - ws.getPaddingLeft() - insets.left);
+            translationX = pageRect.left - childLeft + offsetX / scale;
+        }
+
         return new float[] {scale, translationX, translationY};
     }
+
+    public static float getVerticalProgress(DeviceProfile grid, Context context) {
+        if (!grid.isVerticalBarLayout()) {
+            return 1f;
+        }
+
+        float total = grid.heightPx;
+        float searchHeight = total - grid.availableHeightPx +
+                context.getResources().getDimension(R.dimen.all_apps_search_box_full_height);
+        return 1 - (searchHeight / total);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
new file mode 100644
index 0000000..335077a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.util.TouchController;
+import com.android.quickstep.RecentsView;
+import com.android.quickstep.TaskView;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+
+/**
+ * Touch controller for swipe interaction in Overview state
+ */
+public class OverviewSwipeController extends AnimatorListenerAdapter
+        implements TouchController, SwipeDetector.Listener {
+
+    private static final String TAG = "OverviewSwipeController";
+
+    private static final float ALLOWED_FLING_DIRECTION_CHANGE_PROGRESS = 0.1f;
+    private static final int SINGLE_FRAME_MS = 16;
+
+    // Progress after which the transition is assumed to be a success in case user does not fling
+    private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
+
+    private final Launcher mLauncher;
+    private final SwipeDetector mDetector;
+    private final RecentsView mRecentsView;
+    private final int[] mTempCords = new int[2];
+
+    private AnimatorPlaybackController mCurrentAnimation;
+    private boolean mCurrentAnimationIsGoingUp;
+
+    private boolean mNoIntercept;
+    private boolean mSwipeDownEnabled;
+
+    private float mDisplacementShift;
+    private float mProgressMultiplier;
+    private float mEndDisplacement;
+
+    private TaskView mTaskBeingDragged;
+
+    public OverviewSwipeController(Launcher launcher) {
+        mLauncher = launcher;
+        mRecentsView = launcher.getOverviewPanel();
+        mDetector = new SwipeDetector(launcher, this, SwipeDetector.VERTICAL);
+    }
+
+    private boolean canInterceptTouch() {
+        if (mCurrentAnimation != null) {
+            // If we are already animating from a previous state, we can intercept.
+            return true;
+        }
+        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+            return false;
+        }
+        return mLauncher.isInState(OVERVIEW);
+    }
+
+    private boolean isEventOverHotseat(MotionEvent ev) {
+        if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+            return ev.getY() >
+                    mLauncher.getDragLayer().getHeight() * OVERVIEW.getVerticalProgress(mLauncher);
+        } else {
+            return mLauncher.getDragLayer().isEventOverHotseat(ev);
+        }
+    }
+
+    @Override
+    public void onAnimationCancel(Animator animation) {
+        if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
+            Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
+            mDetector.finishedScrolling();
+            mCurrentAnimation = null;
+        }
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mNoIntercept = !canInterceptTouch();
+            if (mNoIntercept) {
+                return false;
+            }
+
+            // Now figure out which direction scroll events the controller will start
+            // calling the callbacks.
+            final int directionsToDetectScroll;
+            boolean ignoreSlopWhenSettling = false;
+
+            if (mCurrentAnimation != null) {
+                directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+                ignoreSlopWhenSettling = true;
+            } else {
+                mTaskBeingDragged = null;
+                mSwipeDownEnabled = true;
+
+                int currentPage = mRecentsView.getCurrentPage();
+                if (currentPage == 0) {
+                    // User is on home tile
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+                } else {
+                    View view = mRecentsView.getChildAt(currentPage);
+                    if (mLauncher.getDragLayer().isEventOverView(view, ev) &&
+                            view instanceof TaskView) {
+                        // The tile can be dragged down to open the task.
+                        mTaskBeingDragged = (TaskView) view;
+                        directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+                    } else if (isEventOverHotseat(ev)) {
+                        // The hotseat is being dragged
+                        directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+                        mSwipeDownEnabled = false;
+                    } else {
+                        mNoIntercept = true;
+                        return false;
+                    }
+                }
+            }
+
+            mDetector.setDetectableScrollConditions(
+                    directionsToDetectScroll, ignoreSlopWhenSettling);
+        }
+
+        if (mNoIntercept) {
+            return false;
+        }
+
+        onControllerTouchEvent(ev);
+        return mDetector.isDraggingOrSettling();
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        return mDetector.onTouchEvent(ev);
+    }
+
+    private void reinitAnimationController(boolean goingUp) {
+        if (!goingUp && !mSwipeDownEnabled) {
+            goingUp = true;
+        }
+        if (mCurrentAnimation != null && mCurrentAnimationIsGoingUp == goingUp) {
+            // No need to init
+            return;
+        }
+        if (mCurrentAnimation != null) {
+            mCurrentAnimation.setPlayFraction(0);
+        }
+        mCurrentAnimationIsGoingUp = goingUp;
+        float range = mLauncher.getAllAppsController().getShiftRange();
+        long maxDuration = (long) (2 * range);
+        DragLayer dl = mLauncher.getDragLayer();
+
+        if (mTaskBeingDragged == null) {
+            // User is either going to all apps or home
+            mCurrentAnimation = mLauncher.getStateManager()
+                    .createAnimationToNewWorkspace(goingUp ? ALL_APPS : NORMAL, maxDuration);
+            if (goingUp) {
+                mEndDisplacement = -range;
+            } else {
+                View ws = mLauncher.getWorkspace();
+                mTempCords[1] = ws.getHeight() - ws.getPaddingBottom();
+                dl.getDescendantCoordRelativeToSelf(ws, mTempCords);
+
+                float distance = mTempCords[1];
+                if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+                    mTempCords[1] = 0;
+                    dl.getDescendantCoordRelativeToSelf(mLauncher.getHotseat(), mTempCords);
+                    distance = mTempCords[1] - distance;
+                } else {
+                    distance = dl.getHeight() - distance;
+                }
+
+                mEndDisplacement = distance;
+            }
+        } else {
+            if (goingUp) {
+                AnimatorSet anim = new AnimatorSet();
+                ObjectAnimator translate = ObjectAnimator.ofFloat(
+                        mTaskBeingDragged, View.TRANSLATION_Y, -mTaskBeingDragged.getBottom());
+                translate.setInterpolator(LINEAR);
+                translate.setDuration(maxDuration);
+                anim.play(translate);
+
+                ObjectAnimator alpha = ObjectAnimator.ofFloat(mTaskBeingDragged, View.ALPHA, 0);
+                alpha.setInterpolator(DEACCEL_1_5);
+                alpha.setDuration(maxDuration);
+                anim.play(alpha);
+                mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
+                mEndDisplacement = -mTaskBeingDragged.getBottom();
+            } else {
+                AnimatorSet anim = new AnimatorSet();
+                // TODO: Setup a zoom animation
+                mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
+
+                mTempCords[1] = mTaskBeingDragged.getHeight();
+                dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
+                mEndDisplacement = dl.getHeight() - mTempCords[1];
+            }
+        }
+
+        mCurrentAnimation.getTarget().addListener(this);
+        mCurrentAnimation.dispatchOnStart();
+        mProgressMultiplier = 1 / mEndDisplacement;
+    }
+
+    @Override
+    public void onDragStart(boolean start) {
+        if (mCurrentAnimation == null) {
+            reinitAnimationController(mDetector.wasInitialTouchPositive());
+            mDisplacementShift = 0;
+        } else {
+            mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier;
+            mCurrentAnimation.pause();
+        }
+    }
+
+    @Override
+    public boolean onDrag(float displacement, float velocity) {
+        float totalDisplacement = displacement + mDisplacementShift;
+        boolean isGoingUp =
+                totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0;
+        if (isGoingUp != mCurrentAnimationIsGoingUp) {
+            reinitAnimationController(isGoingUp);
+        }
+        mCurrentAnimation.setPlayFraction(totalDisplacement * mProgressMultiplier);
+        return true;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        final boolean goingToEnd;
+
+        if (fling) {
+            boolean goingUp = velocity < 0;
+            if (!goingUp && !mSwipeDownEnabled) {
+                goingToEnd = false;
+            } else if (goingUp != mCurrentAnimationIsGoingUp) {
+                // In case the fling is in opposite direction, make sure if is close enough
+                // from the start position
+                if (mCurrentAnimation.getProgressFraction()
+                        >= ALLOWED_FLING_DIRECTION_CHANGE_PROGRESS) {
+                    // Not allowed
+                    goingToEnd = false;
+                } else {
+                    reinitAnimationController(goingUp);
+                    goingToEnd = true;
+                }
+            } else {
+                goingToEnd = true;
+            }
+        } else {
+            goingToEnd = mCurrentAnimation.getProgressFraction() > SUCCESS_TRANSITION_PROGRESS;
+        }
+
+        float progress = mCurrentAnimation.getProgressFraction();
+        long animationDuration = SwipeDetector.calculateDuration(
+                velocity, goingToEnd ? (1 - progress) : progress);
+
+        float nextFrameProgress = Utilities.boundToRange(
+                progress + velocity * SINGLE_FRAME_MS / Math.abs(mEndDisplacement), 0f, 1f);
+
+
+        mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd));
+
+        ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
+        anim.setFloatValues(nextFrameProgress, goingToEnd ? 1f : 0f);
+        anim.setDuration(animationDuration);
+        anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
+        anim.start();
+    }
+
+    private void onCurrentAnimationEnd(boolean wasSuccess) {
+        // TODO: Might be a good time to log something.
+        if (mTaskBeingDragged == null) {
+            LauncherState state = wasSuccess ?
+                    (mCurrentAnimationIsGoingUp ? ALL_APPS : NORMAL) : OVERVIEW;
+            mLauncher.getStateManager().goToState(state, false);
+        } else if (wasSuccess) {
+            if (mCurrentAnimationIsGoingUp) {
+                mRecentsView.onTaskDismissed(mTaskBeingDragged);
+            } else {
+                mTaskBeingDragged.launchTask(false);
+            }
+        }
+        mDetector.finishedScrolling();
+        mTaskBeingDragged = null;
+        mCurrentAnimation = null;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeUpController.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeUpController.java
index 92f89f6..3ae8f41 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeUpController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeUpController.java
@@ -38,7 +38,15 @@
 
     @Override
     protected boolean shouldInterceptTouch(MotionEvent ev) {
-        return mLauncher.isInState(OVERVIEW) && mLauncher.getDragLayer().isEventOverHotseat(ev);
+        if (!mLauncher.isInState(OVERVIEW)) {
+            return false;
+        }
+        if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+            return ev.getY() >
+                    mLauncher.getDragLayer().getHeight() * OVERVIEW.getVerticalProgress(mLauncher);
+        } else {
+            return mLauncher.getDragLayer().isEventOverHotseat(ev);
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index d657e4e..1465822 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.uioverrides;
 
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -31,9 +34,6 @@
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.RecentsView;
 
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
 public class RecentsViewStateController implements StateHandler {
 
     private final Launcher mLauncher;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
index 410a36f..fb59946 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
 import static com.android.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
+import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -42,6 +43,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.SpringAnimationHandler;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -88,8 +90,9 @@
     private static final int FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE = 1 << 0;
     private static final int FLAG_OVERVIEW_DISABLED_FLING = 1 << 1;
     private static final int FLAG_OVERVIEW_DISABLED_CANCEL_STATE = 1 << 2;
-    private static final int FLAG_RECENTS_PLAN_LOADING = 1 << 3;
     private static final int FLAG_OVERVIEW_DISABLED = 1 << 4;
+    private static final int FLAG_DISABLED_TWO_TARGETS = 1 << 5;
+    private static final int FLAG_DISABLED_BACK_TARGET = 1 << 6;
 
     private final Launcher mLauncher;
     private final SwipeDetector mDetector;
@@ -120,18 +123,25 @@
     }
 
     private boolean canInterceptTouch(MotionEvent ev) {
-        if (!mLauncher.isInState(NORMAL) && !mLauncher.isInState(ALL_APPS)) {
-            // Don't listen for the swipe gesture if we are already in some other state.
-            return false;
-        }
-        if (mAnimatingToOverview) {
-            return false;
-        }
         if (mCurrentAnimation != null) {
             // If we are already animating from a previous state, we can intercept.
             return true;
         }
-        if (mLauncher.isInState(ALL_APPS) && !mLauncher.getAppsView().shouldContainerScroll(ev)) {
+        if (mLauncher.isInState(NORMAL)) {
+            if ((ev.getEdgeFlags() & EDGE_NAV_BAR) != 0 &&
+                    !mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+                // On normal swipes ignore edge swipes
+                return false;
+            }
+        } else if (mLauncher.isInState(ALL_APPS)) {
+            if (!mLauncher.getAppsView().shouldContainerScroll(ev)) {
+                return false;
+            }
+        } else {
+            // Don't listen for the swipe gesture if we are already in some other state.
+            return false;
+        }
+        if (mAnimatingToOverview) {
             return false;
         }
         if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
@@ -237,6 +247,10 @@
 
             mDragPauseDetector = new DragPauseDetector(this::onDragPauseDetected);
             mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE);
+            if (FeatureFlags.ENABLE_TWO_SWIPE_TARGETS) {
+                mDragPauseDetector.addDisabledFlags(FLAG_DISABLED_TWO_TARGETS);
+            }
+
             mOverviewProgressRange = new FloatRange();
             mOverviewProgressRange.start = mLauncher.isInState(NORMAL)
                     ? MIN_PROGRESS_TO_OVERVIEW
@@ -247,22 +261,17 @@
             // Build current animation
             mFromState = mLauncher.getStateManager().getState();
             mToState = mLauncher.isInState(ALL_APPS) ? NORMAL : ALL_APPS;
+
+            if (mToState == NORMAL && mLauncher.getStateManager().getLastState() == OVERVIEW) {
+                mToState = OVERVIEW;
+                mDragPauseDetector.addDisabledFlags(FLAG_DISABLED_BACK_TARGET);
+            }
+
             mTaggedAnimatorSetBuilder = new TaggedAnimatorSetBuilder();
             mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(
                     mToState, mTaggedAnimatorSetBuilder, maxAccuracy);
 
-            if (TouchInteractionService.isConnected()) {
-                // Load recents plan
-                RecentsModel recentsModel = RecentsModel.getInstance(mLauncher);
-                if (recentsModel.getLastLoadPlan() != null) {
-                    onRecentsPlanLoaded(recentsModel.getLastLoadPlan());
-                } else {
-                    mDragPauseDetector.addDisabledFlags(FLAG_RECENTS_PLAN_LOADING);
-                }
-                // Reload again so that we get the latest list
-                // TODO: Use callback instead of polling everytime
-                recentsModel.loadTasks(-1, this::onRecentsPlanLoaded);
-            } else {
+            if (!TouchInteractionService.isConnected()) {
                 mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED);
             }
 
@@ -283,14 +292,6 @@
         }
     }
 
-    private void onRecentsPlanLoaded(RecentsTaskLoadPlan plan) {
-        RecentsView recentsView = mLauncher.getOverviewPanel();
-        recentsView.update(plan);
-        recentsView.initToPage(0);
-
-        mDragPauseDetector.clearDisabledFlags(FLAG_RECENTS_PLAN_LOADING);
-    }
-
     private float getShiftRange() {
         return mLauncher.getAllAppsController().getShiftRange();
     }
@@ -318,32 +319,17 @@
     public void onDragEnd(float velocity, boolean fling) {
         mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_FLING);
 
-        final long animationDuration;
         final int logAction;
         LauncherState targetState;
         final float progress = mCurrentAnimation.getProgressFraction();
 
         if (fling) {
             logAction = Touch.FLING;
-            if (velocity < 0) {
-                targetState = ALL_APPS;
-                animationDuration = SwipeDetector.calculateDuration(velocity,
-                        mToState == ALL_APPS ? (1 - progress) : progress);
-            } else {
-                targetState = NORMAL;
-                animationDuration = SwipeDetector.calculateDuration(velocity,
-                        mToState == ALL_APPS ? progress : (1 - progress));
-            }
+            targetState = velocity < 0 ? ALL_APPS : mLauncher.getStateManager().getLastState();
             // snap to top or bottom using the release velocity
         } else {
             logAction = Touch.SWIPE;
-            if (progress > SUCCESS_TRANSITION_PROGRESS) {
-                targetState = mToState;
-                animationDuration = SwipeDetector.calculateDuration(velocity, 1 - progress);
-            } else {
-                targetState = mFromState;
-                animationDuration = SwipeDetector.calculateDuration(velocity, progress);
-            }
+            targetState = (progress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
         }
 
         if (fling && targetState == ALL_APPS) {
@@ -352,20 +338,32 @@
                 h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
             }
         }
-        mCurrentAnimation.setEndAction(() -> {
-            LauncherState finalState = targetState;
-            if (mDragPauseDetector.isTriggered() && targetState == NORMAL) {
-                finalState = OVERVIEW;
+
+        float endProgress;
+
+        if (mDragPauseDetector.isTriggered() && targetState == NORMAL) {
+            targetState = OVERVIEW;
+            endProgress = OVERVIEW.getVerticalProgress(mLauncher);
+            if (mFromState == NORMAL) {
+                endProgress = 1 - endProgress;
             }
-            onSwipeInteractionCompleted(finalState, logAction);
-        });
+        } else if (targetState == mToState) {
+            endProgress = 1;
+        } else {
+            endProgress = 0;
+        }
+
+        LauncherState targetStateFinal = targetState;
+        mCurrentAnimation.setEndAction(() ->
+                onSwipeInteractionCompleted(targetStateFinal, logAction));
 
         float nextFrameProgress = Utilities.boundToRange(
                 progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
 
         ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
-        anim.setFloatValues(nextFrameProgress, targetState == mToState ? 1f : 0f);
-        anim.setDuration(animationDuration);
+        anim.setFloatValues(nextFrameProgress, endProgress);
+        anim.setDuration(
+                SwipeDetector.calculateDuration(velocity, Math.abs(endProgress - progress)));
         anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
         anim.start();
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 73bf85f..e848688 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -30,6 +30,7 @@
 import com.android.launcher3.graphics.BitmapRenderer;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.widget.WidgetsFullSheet;
+import com.android.quickstep.RecentsView;
 import com.android.systemui.shared.recents.view.RecentsTransition;
 
 public class UiFactory {
@@ -37,14 +38,15 @@
     public static final boolean USE_HARDWARE_BITMAP = false; // FeatureFlags.IS_DOGFOOD_BUILD;
 
     public static TouchController[] createTouchControllers(Launcher launcher) {
-
-        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
-            // TODO: Allow swipe up from overview in transposed layout
-            return new TouchController[] {new TwoStepSwipeController(launcher)};
-        } else {
-            return new TouchController[] {
+        if (FeatureFlags.ENABLE_TWO_SWIPE_TARGETS) {
+            return new TouchController[]{
+                    new EdgeSwipeController(launcher),
                     new TwoStepSwipeController(launcher),
-                    new OverviewSwipeUpController(launcher)};
+                    new OverviewSwipeController(launcher)};
+        } else {
+            return new TouchController[]{
+                    new TwoStepSwipeController(launcher),
+                    new OverviewSwipeController(launcher)};
         }
     }
 
@@ -95,4 +97,9 @@
             return result;
         }
     }
+
+    public static void resetOverview(Launcher launcher) {
+        RecentsView recents = launcher.getOverviewPanel();
+        recents.reset();
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
index 9f7cffe..4816e2a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
@@ -54,6 +54,8 @@
     private View mWorkspaceClickTarget;
     private View mWidgetsButton;
 
+    private boolean mLayoutHorizontal;
+
     public WorkspaceCard(Context context) {
         super(context);
     }
@@ -75,6 +77,7 @@
 
         mWorkspaceClickTarget.setOnClickListener(this);
         mWidgetsButton.setOnClickListener(this);
+        setOnClickListener(this);
     }
 
     @Override
@@ -97,37 +100,76 @@
             return;
         }
 
-        int childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
+        float workspaceWidth = mWorkspace.getNormalChildWidth();
+        float workspaceHeight = mWorkspace.getNormalChildHeight();
 
-        int pageHeight = mWorkspace.getNormalChildHeight() * widthSize /
-                mWorkspace.getNormalChildWidth();
-        mWorkspaceClickTarget.measure(childWidthSpec,
-                MeasureSpec.makeMeasureSpec(pageHeight, MeasureSpec.EXACTLY));
+        int availableWidth = widthSize - getPaddingLeft() - getPaddingRight();
+        float scaleX = availableWidth / workspaceWidth;
 
-        int buttonHeight = heightSize - pageHeight - getPaddingTop() - getPaddingBottom();
-        mWidgetsButton.measure(childWidthSpec,
-                MeasureSpec.makeMeasureSpec(buttonHeight, MeasureSpec.EXACTLY));
+        int availableHeight = heightSize - getPaddingTop() - getPaddingBottom();
+        float scaleY = availableHeight / workspaceHeight;
 
+        if (scaleX < scaleY) {
+            mLayoutHorizontal = false;
+            int childWidthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY);
+
+            int pageHeight = Math.round(workspaceHeight * scaleX);
+            mWorkspaceClickTarget.measure(childWidthSpec,
+                    MeasureSpec.makeMeasureSpec(pageHeight, MeasureSpec.EXACTLY));
+
+            int buttonHeight = availableHeight - pageHeight;
+            mWidgetsButton.measure(childWidthSpec,
+                    MeasureSpec.makeMeasureSpec(buttonHeight, MeasureSpec.EXACTLY));
+        } else {
+            mLayoutHorizontal = true;
+            int childHeightSpec = MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY);
+
+            int pageWidth = Math.round(workspaceWidth * scaleY);
+            mWorkspaceClickTarget.measure(
+                    MeasureSpec.makeMeasureSpec(pageWidth, MeasureSpec.EXACTLY), childHeightSpec);
+
+            int buttonWidth = availableWidth - pageWidth;
+            mWidgetsButton.measure(
+                    MeasureSpec.makeMeasureSpec(buttonWidth, MeasureSpec.EXACTLY), childHeightSpec);
+        }
         setMeasuredDimension(widthSize, heightSize);
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        int y1 = getPaddingTop();
-        int y2 = y1 + mWorkspaceClickTarget.getMeasuredHeight();
+        int x = getPaddingLeft();
+        int y = getPaddingTop();
 
-        mWorkspaceClickTarget.layout(getPaddingLeft(), y1,
-                mWorkspaceClickTarget.getMeasuredWidth(), y2);
-
-        mWidgetsButton.layout(getPaddingLeft(), y2, mWidgetsButton.getMeasuredWidth(),
-                mWidgetsButton.getMeasuredHeight() + y2);
+        if (mLayoutHorizontal) {
+            final View first, second;
+            if (Utilities.isRtl(getResources())) {
+                first = mWidgetsButton;
+                second = mWorkspaceClickTarget;
+            } else {
+                first = mWorkspaceClickTarget;
+                second = mWidgetsButton;
+            }
+            int x2 = x + first.getMeasuredWidth();
+            first.layout(x, y,
+                    x2, y + first.getMeasuredHeight());
+            second.layout(x2, y,
+                    x2 + second.getMeasuredWidth(),
+                    y + second.getMeasuredHeight());
+        } else {
+            int y2 = y + mWorkspaceClickTarget.getMeasuredHeight();
+            mWorkspaceClickTarget.layout(x, y,
+                    x + mWorkspaceClickTarget.getMeasuredWidth(), y2);
+            mWidgetsButton.layout(x, y2,
+                    x + mWidgetsButton.getMeasuredWidth(),
+                    y2 + mWidgetsButton.getMeasuredHeight());
+        }
 
         mUIDataValid = false;
     }
 
     @Override
     public void onClick(View view) {
-        if (view == mWorkspaceClickTarget) {
+        if (view == mWorkspaceClickTarget || view == this) {
             mLauncher.getStateManager().goToState(NORMAL);
         } else if (view == mWidgetsButton) {
             WidgetsFullSheet.show(mLauncher, true);
@@ -165,7 +207,7 @@
             mWorkspace.setScaleY(mEvaluatedFloats[0]);
             mWorkspace.setTranslationX(mEvaluatedFloats[1]);
             mWorkspace.setTranslationY(mEvaluatedFloats[2]);
-            translateX += mEvaluatedFloats[1];
+            translateX += mEvaluatedFloats[1] - mScaleAndTranslatePage0[1];
         }
 
         setTranslationX(translateX);
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
index 09fd8f0..168c1fe 100644
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Handler;
@@ -33,14 +34,15 @@
 import android.view.ViewTreeObserver.OnPreDrawListener;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 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;
@@ -55,17 +57,14 @@
 public class NavBarSwipeInteractionHandler extends InternalStateHandler {
 
     private static final int STATE_LAUNCHER_READY = 1 << 0;
-    private static final int STATE_RECENTS_DELAY_COMPLETE = 1 << 1;
-    private static final int STATE_LOAD_PLAN_READY = 1 << 2;
-    private static final int STATE_RECENTS_FULLY_VISIBLE = 1 << 3;
     private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE = 1 << 4;
     private static final int STATE_SCALED_SNAPSHOT_RECENTS = 1 << 5;
     private static final int STATE_SCALED_SNAPSHOT_APP = 1 << 6;
 
-    private static final long RECENTS_VIEW_VISIBILITY_DELAY = 120;
     private static final long RECENTS_VIEW_VISIBILITY_DURATION = 150;
     private static final long MAX_SWIPE_DURATION = 200;
     private static final long MIN_SWIPE_DURATION = 80;
+    private static final int QUICK_SWITCH_SNAP_DURATION = 120;
 
     // Ideal velocity for a smooth transition
     private static final float PIXEL_PER_MS = 2f;
@@ -88,7 +87,7 @@
     // animated to 1, so allow for a smooth transition.
     private final AnimatedFloat mActivityMultiplier = new AnimatedFloat(this::updateFinalShift);
 
-    private final int mRunningTaskId;
+    private final Task mRunningTask;
     private final Context mContext;
 
     private final MultiStateCallback mStateCallback;
@@ -97,29 +96,44 @@
     private SnapshotDragView mDragView;
     private RecentsView mRecentsView;
     private RecentsViewStateController mStateController;
+    private QuickScrubController mQuickScrubController;
     private Hotseat mHotseat;
     private AllAppsScrim mAllAppsScrim;
-    private RecentsTaskLoadPlan mLoadPlan;
 
     private boolean mLauncherReady;
     private boolean mTouchEndHandled;
     private float mCurrentDisplacement;
+    private @TouchInteractionService.InteractionType int mInteractionType;
+    private boolean mStartedQuickScrubFromHome;
 
     private Bitmap mTaskSnapshot;
 
-    NavBarSwipeInteractionHandler(RunningTaskInfo runningTaskInfo, Context context) {
-        mRunningTaskId = runningTaskInfo.id;
+    NavBarSwipeInteractionHandler(RunningTaskInfo runningTaskInfo, Context context,
+            @TouchInteractionService.InteractionType int interactionType) {
+        // TODO: We need a better way for this
+        TaskKey taskKey = new TaskKey(runningTaskInfo.id, 0, null, UserHandle.myUserId(), 0);
+        mRunningTask = new Task(taskKey, null, null, "", "", Color.BLACK, Color.BLACK,
+                true, false, false, false, null, 0, null, false);
+
         mContext = context;
+        mInteractionType = interactionType;
         WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
 
+        DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext);
+        // TODO: If in multi window mode, dp = dp.getMultiWindowProfile()
+        dp = dp.copy(mContext);
+        // TODO: Use different insets for multi-window mode
+        dp.updateInsets(mStableInsets);
+        RecentsView.getPageRect(dp, mContext, mTargetRect);
+        mSourceRect.set(0, 0, dp.widthPx - mStableInsets.left - mStableInsets.right,
+                dp.heightPx - mStableInsets.top - mStableInsets.bottom);
+
         // Build the state callback
         mStateCallback = new MultiStateCallback();
         mStateCallback.addCallback(STATE_LAUNCHER_READY, this::onLauncherReady);
-        mStateCallback.addCallback(STATE_LOAD_PLAN_READY | STATE_RECENTS_DELAY_COMPLETE,
-                this::setTaskPlanToUi);
         mStateCallback.addCallback(STATE_SCALED_SNAPSHOT_APP, this::resumeLastTask);
-        mStateCallback.addCallback(STATE_RECENTS_FULLY_VISIBLE | STATE_SCALED_SNAPSHOT_RECENTS
-                | STATE_ACTIVITY_MULTIPLIER_COMPLETE,
+        mStateCallback.addCallback(
+                STATE_SCALED_SNAPSHOT_RECENTS | STATE_ACTIVITY_MULTIPLIER_COMPLETE,
                 this::onAnimationToLauncherComplete);
         mStateCallback.addCallback(STATE_LAUNCHER_READY | STATE_SCALED_SNAPSHOT_APP,
                 this::cleanupLauncher);
@@ -129,10 +143,6 @@
         mLauncherReady = true;
         executeFrameUpdate();
 
-        // Wait for some time before loading recents so that the first frame is fast
-        new Handler().postDelayed(() -> mStateCallback.setState(STATE_RECENTS_DELAY_COMPLETE),
-                RECENTS_VIEW_VISIBILITY_DELAY);
-
         long duration = Math.min(MAX_SWIPE_DURATION,
                 Math.max((long) (-mCurrentDisplacement / PIXEL_PER_MS), MIN_SWIPE_DURATION));
         if (mCurrentShift.getCurrentAnimation() != null) {
@@ -173,23 +183,39 @@
 
     @Override
     protected void init(Launcher launcher, boolean alreadyOnHome) {
-        AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
-        launcher.getStateManager().goToState(LauncherState.OVERVIEW, alreadyOnHome);
-
         mLauncher = launcher;
-        mDragView = new SnapshotDragView(mLauncher, mTaskSnapshot);
-        mLauncher.getDragLayer().addView(mDragView);
-        mDragView.setPivotX(0);
-        mDragView.setPivotY(0);
-        mRecentsView = mLauncher.getOverviewPanel();
+        mRecentsView = launcher.getOverviewPanel();
+        mRecentsView.showTask(mRunningTask);
         mStateController = mRecentsView.getStateController();
         mHotseat = mLauncher.getHotseat();
         mAllAppsScrim = mLauncher.findViewById(R.id.all_apps_scrim);
 
+        AbstractFloatingView.closeAllOpenViews(mLauncher, alreadyOnHome);
+        mLauncher.getStateManager().goToState(LauncherState.OVERVIEW, alreadyOnHome);
+
+        mDragView = new SnapshotDragView(mLauncher, mTaskSnapshot);
+        mLauncher.getDragLayer().addView(mDragView);
+        mDragView.setPivotX(0);
+        mDragView.setPivotY(0);
+
+        boolean interactionIsQuick
+                = mInteractionType == TouchInteractionService.INTERACTION_QUICK_SCRUB
+                || mInteractionType == TouchInteractionService.INTERACTION_QUICK_SWITCH;
+        mStartedQuickScrubFromHome = alreadyOnHome && interactionIsQuick;
+        if (interactionIsQuick) {
+            mQuickScrubController = mRecentsView.getQuickScrubController();
+            mQuickScrubController.onQuickScrubStart(mStartedQuickScrubFromHome);
+            animateToProgress(1f, MAX_SWIPE_DURATION);
+            if (mStartedQuickScrubFromHome) {
+                mDragView.setVisibility(View.INVISIBLE);
+            }
+        }
+
         // Optimization
-        mLauncher.getAppsView().setVisibility(View.GONE);
-        mStateController.setTransitionProgress(1);
-        mStateController.setVisibility(false);
+        if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+            // All-apps search box is visible in vertical bar layout.
+            mLauncher.getAppsView().setVisibility(View.GONE);
+        }
         TraceHelper.partitionSection("TouchInt", "Launcher on new intent");
     }
 
@@ -211,17 +237,10 @@
 
     @UiThread
     private void updateFinalShift() {
-        if (!mLauncherReady) {
+        if (!mLauncherReady || mStartedQuickScrubFromHome) {
             return;
         }
 
-        if (mTargetRect.isEmpty()) {
-            RecentsView.getPageRect(mLauncher, mTargetRect);
-            DragLayer dl = mLauncher.getDragLayer();
-            mSourceRect.set(0, 0, dl.getWidth() - mStableInsets.left - mStableInsets.right,
-                    dl.getHeight() - mStableInsets.top - mStableInsets.bottom);
-        }
-
         float shift = mCurrentShift.value * mActivityMultiplier.value;
         int hotseatSize = getHotseatSize();
 
@@ -248,26 +267,6 @@
     }
 
     @UiThread
-    public void setRecentsTaskLoadPlan(RecentsTaskLoadPlan loadPlan) {
-        mLoadPlan = loadPlan;
-        mStateCallback.setState(STATE_LOAD_PLAN_READY);
-    }
-
-    private void setTaskPlanToUi() {
-        mRecentsView.update(mLoadPlan);
-        mRecentsView.initToPage(mRecentsView.getFirstTaskIndex());
-        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
     public void endTouch(float endVelocity) {
         if (mTouchEndHandled) {
             return;
@@ -295,7 +294,12 @@
             }
         }
 
-        ObjectAnimator anim = mCurrentShift.animateToValue(endShift).setDuration(duration);
+        animateToProgress(endShift, duration);
+    }
+
+    /** Animates to the given progress, where 0 is the current app and 1 is overview. */
+    private void animateToProgress(float progress, long duration) {
+        ObjectAnimator anim = mCurrentShift.animateToValue(progress).setDuration(duration);
         anim.setInterpolator(Interpolators.SCROLL);
         anim.addListener(new AnimationSuccessListener() {
             @Override
@@ -309,19 +313,16 @@
 
     @UiThread
     private void resumeLastTask() {
-        TaskKey key = null;
-        if (mLoadPlan != null) {
-            Task task = mLoadPlan.getTaskStack().findTaskWithId(mRunningTaskId);
+        // TODO: We need a better way for this
+        TaskKey key = mRunningTask.key;
+        RecentsTaskLoadPlan loadPlan = RecentsModel.getInstance(mContext).getLastLoadPlan();
+        if (loadPlan != null) {
+            Task task = loadPlan.getTaskStack().findTaskWithId(key.id);
             if (task != null) {
                 key = task.key;
             }
         }
 
-        if (key == null) {
-            // TODO: We need a better way for this
-            key = new TaskKey(mRunningTaskId, 0, null, UserHandle.myUserId(), 0);
-        }
-
         ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
         ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(key, opts, null, null);
     }
@@ -339,5 +340,33 @@
         if (currentRecentsPage instanceof TaskView) {
             ((TaskView) currentRecentsPage).animateIconToScale(1f);
         }
+        if (mInteractionType == TouchInteractionService.INTERACTION_QUICK_SWITCH) {
+            for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
+                TaskView taskView = (TaskView) mRecentsView.getPageAt(i);
+                // TODO: Match the keys directly
+                if (taskView.getTask().key.id != mRunningTask.key.id) {
+                    mRecentsView.snapToPage(i, QUICK_SWITCH_SNAP_DURATION);
+                    taskView.postDelayed(() -> {taskView.launchTask(true);},
+                            QUICK_SWITCH_SNAP_DURATION);
+                    break;
+                }
+            }
+        } else if (mInteractionType == TouchInteractionService.INTERACTION_QUICK_SCRUB) {
+            if (mQuickScrubController != null) {
+                mQuickScrubController.snapToPageForCurrentQuickScrubSection();
+            }
+        }
+    }
+
+    public void onQuickScrubEnd() {
+        if (mQuickScrubController != null) {
+            mQuickScrubController.onQuickScrubEnd();
+        }
+    }
+
+    public void onQuickScrubProgress(float progress) {
+        if (mQuickScrubController != null) {
+            mQuickScrubController.onQuickScrubProgress(progress);
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
new file mode 100644
index 0000000..f4c2055
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import android.view.HapticFeedbackConstants;
+
+import com.android.launcher3.Alarm;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.OnAlarmListener;
+
+/**
+ * Responds to quick scrub callbacks to page through and launch recent tasks.
+ *
+ * The behavior is to evenly divide the progress into sections, each of which scrolls one page.
+ * The first and last section set an alarm to auto-advance backwards or forwards, respectively.
+ */
+public class QuickScrubController implements OnAlarmListener {
+
+    private static final int NUM_QUICK_SCRUB_SECTIONS = 5;
+    private static final long AUTO_ADVANCE_DELAY = 500;
+    private static final int QUICKSCRUB_END_SNAP_DURATION_PER_PAGE = 60;
+
+    private Launcher mLauncher;
+    private Alarm mAutoAdvanceAlarm;
+    private RecentsView mRecentsView;
+
+    private int mQuickScrubSection;
+    private int mStartPage;
+
+    public QuickScrubController(Launcher launcher) {
+        mLauncher = launcher;
+        mAutoAdvanceAlarm = new Alarm();
+        mAutoAdvanceAlarm.setOnAlarmListener(this);
+    }
+
+    public void onQuickScrubStart(boolean startingFromHome) {
+        mRecentsView = mLauncher.getOverviewPanel();
+        mStartPage = startingFromHome ? 0 : mRecentsView.getFirstTaskIndex();
+        mQuickScrubSection = 0;
+    }
+
+    public void onQuickScrubEnd() {
+        mAutoAdvanceAlarm.cancelAlarm();
+        if (mRecentsView != null) {
+            int page = mRecentsView.getNextPage();
+            // Settle on the page then launch it.
+            int snapDuration = Math.abs(page - mRecentsView.getPageNearestToCenterOfScreen())
+                    * QUICKSCRUB_END_SNAP_DURATION_PER_PAGE;
+            mRecentsView.snapToPage(page, snapDuration);
+            mRecentsView.postDelayed(() -> {
+                if (page < mRecentsView.getFirstTaskIndex()) {
+                    mRecentsView.getPageAt(page).performClick();
+                } else {
+                    ((TaskView) mRecentsView.getPageAt(page)).launchTask(true);
+                }
+            }, snapDuration);
+        }
+    }
+
+    public void onQuickScrubProgress(float progress) {
+        int quickScrubSection = Math.round(progress * NUM_QUICK_SCRUB_SECTIONS);
+        if (quickScrubSection != mQuickScrubSection) {
+            int pageToGoTo = mRecentsView.getNextPage() + quickScrubSection - mQuickScrubSection;
+            goToPageWithHaptic(pageToGoTo);
+            if (quickScrubSection == NUM_QUICK_SCRUB_SECTIONS || quickScrubSection == 0) {
+                mAutoAdvanceAlarm.setAlarm(AUTO_ADVANCE_DELAY);
+            } else {
+                mAutoAdvanceAlarm.cancelAlarm();
+            }
+            mQuickScrubSection = quickScrubSection;
+        }
+    }
+
+    public void snapToPageForCurrentQuickScrubSection() {
+        goToPageWithHaptic(mRecentsView.getCurrentPage() + mQuickScrubSection);
+    }
+
+    private void goToPageWithHaptic(int pageToGoTo) {
+        if (pageToGoTo != mRecentsView.getNextPage()) {
+            mRecentsView.snapToPage(pageToGoTo);
+            mRecentsView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP,
+                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+        }
+    }
+
+    @Override
+    public void onAlarm(Alarm alarm) {
+        int currPage = mRecentsView.getNextPage();
+        if (mQuickScrubSection == NUM_QUICK_SCRUB_SECTIONS
+                && currPage < mRecentsView.getPageCount() - 1) {
+            goToPageWithHaptic(currPage + 1);
+        } else if (mQuickScrubSection == 0 && currPage > mStartPage) {
+            goToPageWithHaptic(currPage - 1);
+        }
+        mAutoAdvanceAlarm.setAlarm(AUTO_ADVANCE_DELAY);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 112f156..22658b2 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -15,8 +15,10 @@
  */
 package com.android.quickstep;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
+import android.os.Build;
 import android.os.Looper;
 import android.os.UserHandle;
 
@@ -25,7 +27,9 @@
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
 import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
@@ -33,7 +37,8 @@
 /**
  * Singleton class to load and manage recents model.
  */
-public class RecentsModel {
+@TargetApi(Build.VERSION_CODES.O)
+public class RecentsModel extends TaskStackChangeListener {
 
     // We do not need any synchronization for this variable as its only written on UI thread.
     private static RecentsModel INSTANCE;
@@ -59,6 +64,9 @@
     private final MainThreadExecutor mMainThreadExecutor;
 
     private RecentsTaskLoadPlan mLastLoadPlan;
+    private int mLastLoadPlanId;
+    private int mTaskChangeId;
+
     private RecentsModel(Context context) {
         mContext = context;
 
@@ -69,6 +77,10 @@
         mRecentsTaskLoader.startLoader(mContext);
 
         mMainThreadExecutor = new MainThreadExecutor();
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
+
+        mTaskChangeId = 1;
+        loadTasks(-1, null);
     }
 
     public RecentsTaskLoader getRecentsTaskLoader() {
@@ -80,8 +92,20 @@
      * @param taskId The running task id or -1
      * @param callback The callback to receive the task plan once its complete or null. This is
      *                always called on the UI thread.
+     * @return the request id associated with this call.
      */
-    public void loadTasks(int taskId, Consumer<RecentsTaskLoadPlan> callback) {
+    public int loadTasks(int taskId, Consumer<RecentsTaskLoadPlan> callback) {
+        final int requestId = mTaskChangeId;
+
+        // Fail fast if nothing has changed.
+        if (mLastLoadPlanId == mTaskChangeId) {
+            if (callback != null) {
+                final RecentsTaskLoadPlan plan = mLastLoadPlan;
+                mMainThreadExecutor.execute(() -> callback.accept(plan));
+            }
+            return requestId;
+        }
+
         BackgroundExecutor.get().submit(() -> {
             // Preload the plan
             RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(mContext);
@@ -91,11 +115,23 @@
             // Set the load plan on UI thread
             mMainThreadExecutor.execute(() -> {
                 mLastLoadPlan = loadPlan;
+                mLastLoadPlanId = requestId;
+
                 if (callback != null) {
                     callback.accept(loadPlan);
                 }
             });
         });
+        return requestId;
+    }
+
+    @Override
+    public void onTaskStackChanged() {
+        mTaskChangeId++;
+    }
+
+    public boolean isLoadPlanValid(int resultId) {
+        return mTaskChangeId == resultId;
     }
 
     public RecentsTaskLoadPlan getLastLoadPlan() {
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
index 00901c6..d1bbd23 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -25,10 +25,9 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
-import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.uioverrides.OverviewState;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.shared.recents.model.RecentsTaskLoader;
@@ -41,6 +40,8 @@
 
 import java.util.ArrayList;
 
+import static com.android.launcher3.LauncherState.NORMAL;
+
 /**
  * A list of recent tasks.
  */
@@ -50,11 +51,16 @@
     public static final int SCROLL_TYPE_TASK = 1;
     public static final int SCROLL_TYPE_WORKSPACE = 2;
 
+    private final Launcher mLauncher;
+    private QuickScrubController mQuickScrubController;
     private final ScrollState mScrollState = new ScrollState();
     private boolean mOverviewStateEnabled;
     private boolean mTaskStackListenerRegistered;
     private LayoutTransition mLayoutTransition;
 
+    /**
+     * TODO: Call reloadIdNeeded in onTaskStackChanged.
+     */
     private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
         public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
@@ -71,6 +77,11 @@
     private RecentsViewStateController mStateController;
     private int mFirstTaskIndex;
 
+    private final RecentsModel mModel;
+    private int mLoadPlanId = -1;
+
+    private Task mFirstTask;
+
     public RecentsView(Context context) {
         this(context, null);
     }
@@ -86,6 +97,10 @@
         setClipChildren(true);
         setupLayoutTransition();
 
+        mLauncher = Launcher.getLauncher(context);
+        mQuickScrubController = new QuickScrubController(mLauncher);
+        mModel = RecentsModel.getInstance(context);
+
         mScrollState.isRtl = mIsRtl;
     }
 
@@ -104,9 +119,9 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        Rect padding = getPadding(Launcher.getLauncher(getContext()));
+        Rect padding =
+                getPadding(Launcher.getLauncher(getContext()).getDeviceProfile(), getContext());
         setPadding(padding.left, padding.top, padding.right, padding.bottom);
-
         mFirstTaskIndex = getPageCount();
     }
 
@@ -145,9 +160,8 @@
         updateTaskStackListenerState();
     }
 
-    public void update(RecentsTaskLoadPlan loadPlan) {
-        final RecentsTaskLoader loader = RecentsModel.getInstance(getContext())
-                .getRecentsTaskLoader();
+    private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
+        final RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
         TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
         if (stack == null) {
             removeAllViews();
@@ -157,10 +171,17 @@
         // Ensure there are as many views as there are tasks in the stack (adding and trimming as
         // necessary)
         final LayoutInflater inflater = LayoutInflater.from(getContext());
-        final ArrayList<Task> tasks = stack.getTasks();
+        final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
         setLayoutTransition(null);
-        int requiredChildCount = tasks.size() + mFirstTaskIndex;
 
+        if (mFirstTask != null) {
+            // TODO: Handle this case here once we have a valid implementation for mFirstTask
+            if (tasks.isEmpty() || !keysEquals(tasks.get(tasks.size() - 1), mFirstTask)) {
+                // tasks.add(mFirstTask);
+            }
+        }
+
+        final int requiredChildCount = tasks.size() + mFirstTaskIndex;
         for (int i = getChildCount(); i < requiredChildCount; i++) {
             final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
             addView(taskView);
@@ -181,60 +202,67 @@
         }
     }
 
-    public void initToPage(int pageNo) {
-        setCurrentPage(pageNo);
-        if (getPageAt(mCurrentPage) instanceof TaskView) {
-            ((TaskView) getPageAt(mCurrentPage)).setIconScale(0);
-        }
-    }
-
-    public void launchTaskWithId(int taskId) {
-        for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
-            final TaskView taskView = (TaskView) getChildAt(i);
-            if (taskView.getTask().key.id == taskId) {
-                taskView.launchTask(false /* animate */);
-                return;
-            }
-        }
-    }
-
     private void updateTaskStackListenerState() {
         boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
                 && getWindowVisibility() == VISIBLE;
         if (registerStackListener != mTaskStackListenerRegistered) {
             if (registerStackListener) {
-                ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+                ActivityManagerWrapper.getInstance()
+                        .registerTaskStackListener(mTaskStackListener);
+                reloadIfNeeded();
             } else {
-                ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+                ActivityManagerWrapper.getInstance()
+                        .unregisterTaskStackListener(mTaskStackListener);
             }
             mTaskStackListenerRegistered = registerStackListener;
         }
     }
 
-    private static Rect getPadding(Launcher launcher) {
-        DeviceProfile profile = launcher.getDeviceProfile();
+    private static Rect getPadding(DeviceProfile profile, Context context) {
         Rect stableInsets = new Rect();
         WindowManagerWrapper.getInstance().getStableInsets(stableInsets);
-        Rect padding = profile.getWorkspacePadding(null);
-        float taskWidth = profile.getCurrentWidth() - stableInsets.left - stableInsets.right;
-        float taskHeight = profile.getCurrentHeight() - stableInsets.top - stableInsets.bottom;
-        float overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
-                - stableInsets.top;
-        float overviewWidth = taskWidth * overviewHeight / taskHeight;
+        Rect padding = new Rect(profile.workspacePadding);
+
+        float taskWidth = profile.widthPx - stableInsets.left - stableInsets.right;
+        float taskHeight = profile.heightPx - stableInsets.top - stableInsets.bottom;
+
+        float overviewHeight, overviewWidth;
+        if (profile.isVerticalBarLayout()) {
+            // Use the same padding on both sides for symmetry.
+            float availableWidth = taskWidth - 2 * Math.max(padding.left, padding.right);
+            float availableHeight = profile.availableHeightPx - padding.top - padding.bottom
+                    - stableInsets.top
+                    - profile.heightPx * (1 - OverviewState.getVerticalProgress(profile, context));
+
+            float scaledRatio = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
+            overviewHeight = taskHeight * scaledRatio;
+            overviewWidth = taskWidth * scaledRatio;
+
+        } else {
+            overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
+                    - stableInsets.top;
+            overviewWidth = taskWidth * overviewHeight / taskHeight;
+        }
+
+        padding.bottom = profile.availableHeightPx - padding.top - stableInsets.top
+                - Math.round(overviewHeight);
         padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
         return padding;
     }
 
     public static void getPageRect(Launcher launcher, Rect outRect) {
-        DragLayer dl = launcher.getDragLayer();
-        Rect targetPadding = getPadding(launcher);
-        Rect insets = dl.getInsets();
+        getPageRect(launcher.getDeviceProfile(), launcher, outRect);
+    }
+
+    public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
+        Rect targetPadding = getPadding(grid, context);
+        Rect insets = grid.getInsets();
         outRect.set(
                 targetPadding.left + insets.left,
                 targetPadding.top + insets.top,
-                dl.getWidth() - targetPadding.right - insets.right,
-                dl.getHeight() - targetPadding.bottom - insets.bottom);
-        outRect.top += launcher.getResources()
+                grid.widthPx - targetPadding.right - insets.right,
+                grid.heightPx - targetPadding.bottom - insets.bottom);
+        outRect.top += context.getResources()
                 .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
     }
 
@@ -272,11 +300,78 @@
     public void onTaskDismissed(TaskView taskView) {
         ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
         removeView(taskView);
-        if (getChildCount() == mFirstTaskIndex) {
-            Launcher.getLauncher(getContext()).getStateManager().goToState(LauncherState.NORMAL);
+        if (getTaskCount() == 0) {
+            mLauncher.getStateManager().goToState(NORMAL);
         }
     }
 
+    public void reset() {
+        mFirstTask = null;
+        setCurrentPage(0);
+    }
+
+    public int getTaskCount() {
+        return getChildCount() - mFirstTaskIndex;
+    }
+
+    /**
+     * Reloads the view if anything in recents changed.
+     */
+    public void reloadIfNeeded() {
+        if (!mModel.isLoadPlanValid(mLoadPlanId)) {
+            int taskId = -1;
+            if (mFirstTask != null) {
+                taskId = mFirstTask.key.id;
+            }
+            mLoadPlanId = mModel.loadTasks(taskId, this::applyLoadPlan);
+        }
+    }
+
+    /**
+     * Ensures that the first task in the view represents {@param task} and reloads the view
+     * if needed. This allows the swipe-up gesture to assume that the first tile always
+     * corresponds to the correct task.
+     * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
+     * is called.
+     * Also scrolls the view to this task
+     */
+    public void showTask(Task task) {
+        boolean needsReload = false;
+        boolean inflateFirstChild = true;
+        if (getTaskCount() > 0) {
+            TaskView tv = (TaskView) getChildAt(mFirstTaskIndex);
+            inflateFirstChild = !keysEquals(tv.getTask(), task);
+        }
+        if (inflateFirstChild) {
+            needsReload = true;
+            setLayoutTransition(null);
+            // Add an empty view for now
+            final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
+                    .inflate(R.layout.task, this, false);
+            addView(taskView, mFirstTaskIndex);
+            taskView.bind(task);
+            setLayoutTransition(mLayoutTransition);
+        }
+        if (!needsReload) {
+            needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
+        }
+        if (needsReload) {
+            mLoadPlanId = mModel.loadTasks(task.key.id, this::applyLoadPlan);
+        }
+        mFirstTask = task;
+        setCurrentPage(mFirstTaskIndex);
+        ((TaskView) getPageAt(mCurrentPage)).setIconScale(0);
+    }
+
+    private static boolean keysEquals(Task t1, Task t2) {
+        // TODO: Match the keys directly
+        return t1.key.id == t2.key.id;
+    }
+
+    public QuickScrubController getQuickScrubController() {
+        return mQuickScrubController;
+    }
+
     public interface PageCallbacks {
 
         /**
diff --git a/quickstep/src/com/android/quickstep/TaskMenuView.java b/quickstep/src/com/android/quickstep/TaskMenuView.java
index 70542c2..bf75376 100644
--- a/quickstep/src/com/android/quickstep/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/TaskMenuView.java
@@ -25,7 +25,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewOutlineProvider;
@@ -61,6 +60,8 @@
     private Launcher mLauncher;
     private TextView mTaskIconAndName;
     private AnimatorSet mOpenCloseAnimator;
+    private TaskView mTaskView;
+    private View mWidgetsOptionView;
 
     public TaskMenuView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -130,8 +131,9 @@
             return false;
         }
         mLauncher.getDragLayer().addView(this);
-        addMenuOptions(taskView.getTask());
-        orientAroundTaskView(taskView);
+        mTaskView = taskView;
+        addMenuOptions(mTaskView.getTask());
+        orientAroundTaskView(mTaskView);
         post(this::animateOpen);
         return true;
     }
@@ -143,20 +145,27 @@
         mTaskIconAndName.setCompoundDrawables(null, icon, null, null);
         mTaskIconAndName.setText(TaskUtils.getTitle(mLauncher, task));
 
-        LayoutInflater inflater = mLauncher.getLayoutInflater();
         for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
             OnClickListener onClickListener = menuOption.getOnClickListener(mLauncher, task);
             if (onClickListener != null) {
-                DeepShortcutView menuOptionView = (DeepShortcutView) inflater.inflate(
-                        R.layout.system_shortcut, this, false);
-                menuOptionView.getIconView().setBackgroundResource(menuOption.iconResId);
-                menuOptionView.getBubbleText().setText(menuOption.labelResId);
-                menuOptionView.setOnClickListener(onClickListener);
-                addView(menuOptionView);
+                addMenuOption(menuOption, onClickListener);
             }
         }
     }
 
+    private void addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener) {
+        DeepShortcutView menuOptionView = (DeepShortcutView) mLauncher.getLayoutInflater().inflate(
+                R.layout.system_shortcut, this, false);
+        menuOptionView.getIconView().setBackgroundResource(menuOption.iconResId);
+        menuOptionView.getBubbleText().setText(menuOption.labelResId);
+        menuOptionView.setOnClickListener(onClickListener);
+        addView(menuOptionView);
+
+        if (menuOption instanceof TaskSystemShortcut.Widgets) {
+            mWidgetsOptionView = menuOptionView;
+        }
+    }
+
     private void orientAroundTaskView(TaskView taskView) {
         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
         mLauncher.getDragLayer().getDescendantRectRelativeToSelf(taskView, sTempRect);
@@ -220,4 +229,19 @@
             }
         };
     }
+
+    @Override
+    protected void onWidgetsBound() {
+        TaskSystemShortcut widgetsOption = new TaskSystemShortcut.Widgets();
+        View.OnClickListener onClickListener = widgetsOption.getOnClickListener(
+                mLauncher, mTaskView.getTask());
+
+        if (onClickListener != null && mWidgetsOptionView == null) {
+            // We didn't have any widgets cached but now there are some, so add the option.
+            addMenuOption(widgetsOption, onClickListener);
+        } else if (onClickListener == null && mWidgetsOptionView != null) {
+            // No widgets exist, but we previously added the option so remove it.
+            removeView(mWidgetsOptionView);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
index 3d4d451..473681f 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
@@ -161,7 +161,7 @@
                     // Scale the landscape thumbnail up to app size, then scale that to the task
                     // view size to match other portrait screenshots
                     mThumbnailScale = invThumbnailScale *
-                            ((float) getMeasuredWidth() / profile.getCurrentWidth());
+                            ((float) getMeasuredWidth() / profile.widthPx);
                 } else {
                     // Otherwise, scale the screenshot to fit 1:1 in the current orientation
                     mThumbnailScale = invThumbnailScale;
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
index 94d85ee..3f733ca 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -16,29 +16,17 @@
 
 package com.android.quickstep;
 
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Property;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.touch.SwipeDetector;
 import com.android.quickstep.RecentsView.PageCallbacks;
 import com.android.quickstep.RecentsView.ScrollState;
 import com.android.systemui.shared.recents.model.Task;
@@ -52,11 +40,13 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
+
 /**
  * A task in the Recents view.
  */
-public class TaskView extends FrameLayout implements TaskCallbacks, SwipeDetector.Listener,
-        PageCallbacks {
+public class TaskView extends FrameLayout implements TaskCallbacks, PageCallbacks {
 
     /** Designates how "curvy" the carousel is from 0 to 1, where 0 is a straight line. */
     private static final float CURVE_FACTOR = 0.25f;
@@ -70,30 +60,8 @@
      */
     private static final float MAX_PAGE_SCRIM_ALPHA = 0.8f;
 
-    private static final int SWIPE_DIRECTIONS = SwipeDetector.DIRECTION_POSITIVE;
-
-    /**
-     * The task will appear fully dismissed when the distance swiped
-     * reaches this percentage of the card height.
-     */
-    private static final float SWIPE_DISTANCE_HEIGHT_PERCENTAGE = 0.38f;
-
     private static final long SCALE_ICON_DURATION = 120;
 
-    private static final Property<TaskView, Float> PROPERTY_SWIPE_PROGRESS =
-            new Property<TaskView, Float>(Float.class, "swipe_progress") {
-
-                @Override
-                public Float get(TaskView taskView) {
-                    return taskView.mSwipeProgress;
-                }
-
-                @Override
-                public void set(TaskView taskView, Float progress) {
-                    taskView.setSwipeProgress(progress);
-                }
-            };
-
     private static final Property<TaskView, Float> SCALE_ICON_PROPERTY =
             new Property<TaskView, Float>(Float.TYPE, "scale_icon") {
                 @Override
@@ -110,11 +78,6 @@
     private Task mTask;
     private TaskThumbnailView mSnapshotView;
     private ImageView mIconView;
-    private SwipeDetector mSwipeDetector;
-    private float mSwipeDistance;
-    private float mSwipeProgress;
-    private Interpolator mAlphaInterpolator;
-    private Interpolator mSwipeAnimInterpolator;
     private float mIconScale = 1f;
 
     public TaskView(Context context) {
@@ -130,11 +93,6 @@
         setOnClickListener((view) -> {
             launchTask(true /* animate */);
         });
-
-        mSwipeDetector = new SwipeDetector(getContext(), this, SwipeDetector.VERTICAL);
-        mSwipeDetector.setDetectableScrollConditions(SWIPE_DIRECTIONS, false);
-        mAlphaInterpolator = Interpolators.ACCEL_1_5;
-        mSwipeAnimInterpolator = Interpolators.SCROLL_CUBIC;
     }
 
     @Override
@@ -144,15 +102,6 @@
         mIconView = findViewById(R.id.icon);
     }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-        View p = (View) getParent();
-        mSwipeDistance = (getMeasuredHeight() - p.getPaddingTop() - p.getPaddingBottom())
-                * SWIPE_DISTANCE_HEIGHT_PERCENTAGE;
-    }
-
     /**
      * Updates this task view to the given {@param task}.
      */
@@ -223,80 +172,6 @@
         // Do nothing
     }
 
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        mSwipeDetector.onTouchEvent(ev);
-        return super.onInterceptTouchEvent(ev);
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        mSwipeDetector.onTouchEvent(event);
-        return mSwipeDetector.isDraggingOrSettling() || super.onTouchEvent(event);
-    }
-
-    // Swipe detector methods
-
-    @Override
-    public void onDragStart(boolean start) {
-        getParent().requestDisallowInterceptTouchEvent(true);
-    }
-
-    @Override
-    public boolean onDrag(float displacement, float velocity) {
-        setSwipeProgress(Utilities.boundToRange(displacement / mSwipeDistance,
-                allowsSwipeUp() ? -1 : 0, allowsSwipeDown() ? 1 : 0));
-        return true;
-    }
-
-    /**
-     * Indicates the page is being removed.
-     * @param progress Ranges from -1 (fading upwards) to 1 (fading downwards).
-     */
-    private void setSwipeProgress(float progress) {
-        mSwipeProgress = progress;
-        float translationY = mSwipeProgress * mSwipeDistance;
-        float alpha = 1f - mAlphaInterpolator.getInterpolation(Math.abs(mSwipeProgress));
-        // Only change children to avoid changing our properties while dragging.
-        mIconView.setTranslationY(translationY);
-        mSnapshotView.setTranslationY(translationY);
-        mIconView.setAlpha(alpha);
-        mSnapshotView.setAlpha(alpha);
-    }
-
-    private boolean allowsSwipeUp() {
-        return (SWIPE_DIRECTIONS & SwipeDetector.DIRECTION_POSITIVE) != 0;
-    }
-
-    private boolean allowsSwipeDown() {
-        return (SWIPE_DIRECTIONS & SwipeDetector.DIRECTION_NEGATIVE) != 0;
-    }
-
-    @Override
-    public void onDragEnd(float velocity, boolean fling) {
-        boolean movingAwayFromCenter = velocity < 0 == mSwipeProgress < 0;
-        boolean flingAway = fling && movingAwayFromCenter
-                && (allowsSwipeUp() && velocity < 0 || allowsSwipeDown() && velocity > 0);
-        final boolean shouldRemove = flingAway || (!fling && Math.abs(mSwipeProgress) > 0.5f);
-        float fromProgress = mSwipeProgress;
-        float toProgress = !shouldRemove ? 0f : mSwipeProgress < 0 ? -1f : 1f;
-        ValueAnimator swipeAnimator = ObjectAnimator.ofFloat(this, PROPERTY_SWIPE_PROGRESS,
-                fromProgress, toProgress);
-        swipeAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (shouldRemove) {
-                    ((RecentsView) getParent()).onTaskDismissed(TaskView.this);
-                }
-                mSwipeDetector.finishedScrolling();
-            }
-        });
-        swipeAnimator.setDuration(SwipeDetector.calculateDuration(velocity,
-                Math.abs(toProgress - fromProgress)));
-        swipeAnimator.setInterpolator(mSwipeAnimInterpolator);
-        swipeAnimator.start();
-    }
-
     public void animateIconToScale(float scale) {
         ObjectAnimator.ofFloat(this, SCALE_ICON_PROPERTY, scale)
                 .setDuration(SCALE_ICON_DURATION).start();
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 4321791..4cfa1b8 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -37,6 +37,7 @@
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.support.annotation.IntDef;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.Display;
@@ -56,6 +57,8 @@
 import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.function.Consumer;
 
 /**
@@ -64,8 +67,21 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class TouchInteractionService extends Service {
 
+    public static final int EDGE_NAV_BAR = 1 << 8;
+
     private static final String TAG = "TouchInteractionService";
 
+    @IntDef(flag = true, value = {
+            INTERACTION_NORMAL,
+            INTERACTION_QUICK_SWITCH,
+            INTERACTION_QUICK_SCRUB
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface InteractionType {}
+    public static final int INTERACTION_NORMAL = 0;
+    public static final int INTERACTION_QUICK_SWITCH = 1;
+    public static final int INTERACTION_QUICK_SCRUB = 2;
+
     private final IBinder mMyBinder = new IOverviewProxy.Stub() {
 
         @Override
@@ -77,6 +93,30 @@
         public void onBind(ISystemUiProxy iSystemUiProxy) throws RemoteException {
             mISystemUiProxy = iSystemUiProxy;
         }
+
+        @Override
+        public void onQuickSwitch() {
+            startTouchTracking(INTERACTION_QUICK_SWITCH);
+        }
+
+        @Override
+        public void onQuickScrubStart() {
+            startTouchTracking(INTERACTION_QUICK_SCRUB);
+        }
+
+        @Override
+        public void onQuickScrubEnd() {
+            if (mInteractionHandler != null) {
+                mInteractionHandler.onQuickScrubEnd();
+            }
+        }
+
+        @Override
+        public void onQuickScrubProgress(float progress) {
+            if (mInteractionHandler != null) {
+                mInteractionHandler.onQuickScrubProgress(progress);
+            }
+        }
     };
 
     private final Consumer<MotionEvent> mOtherActivityTouchConsumer
@@ -121,10 +161,10 @@
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         ResolveInfo info = getPackageManager().resolveActivity(mHomeIntent, 0);
         mLauncher = new ComponentName(getPackageName(), info.activityInfo.name);
-        mHomeIntent.setComponent(mLauncher);
+        // Clear the packageName as system can fail to dedupe it b/64108432
+        mHomeIntent.setComponent(mLauncher).setPackage(null);
 
         mEventQueue = new MotionEventQueue(Choreographer.getInstance(), this::handleMotionEvent);
-        mRecentsModel.loadTasks(-1, null);
         sConnected = true;
     }
 
@@ -214,7 +254,7 @@
                 if (mInteractionHandler == null) {
                     if (Math.abs(displacement) >= mTouchSlop) {
                         mStartDisplacement = Math.signum(displacement) * mTouchSlop;
-                        startTouchTracking();
+                        startTouchTracking(INTERACTION_NORMAL);
                     }
                 } else {
                     // Move
@@ -242,10 +282,10 @@
         return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0;
     }
 
-    private void startTouchTracking() {
+    private void startTouchTracking(@InteractionType int interactionType) {
         // Create the shared handler
         final NavBarSwipeInteractionHandler handler =
-                new NavBarSwipeInteractionHandler(mRunningTask, this);
+                new NavBarSwipeInteractionHandler(mRunningTask, this, interactionType);
 
         TraceHelper.partitionSection("TouchInt", "Thershold crossed ");
 
@@ -267,7 +307,7 @@
         });
 
         // Preload the plan
-        mRecentsModel.loadTasks(mRunningTask.id, handler::setRecentsTaskLoadPlan);
+        mRecentsModel.loadTasks(mRunningTask.id, null);
         mInteractionHandler = handler;
     }
 
@@ -381,9 +421,12 @@
         }
 
         private void sendEvent(MotionEvent ev) {
+            int flags = ev.getEdgeFlags();
+            ev.setEdgeFlags(flags | EDGE_NAV_BAR);
             ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
             mTarget.dispatchTouchEvent(ev);
             ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
+            ev.setEdgeFlags(flags);
         }
     }
 }
diff --git a/res/layout-land/all_apps_fast_scroller.xml b/res/layout-land/all_apps_fast_scroller.xml
deleted file mode 100644
index 16aa2af..0000000
--- a/res/layout-land/all_apps_fast_scroller.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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.
--->
-<merge
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <!-- Fast scroller popup -->
-    <TextView
-        android:id="@+id/fast_scroller_popup"
-        style="@style/FastScrollerPopup"
-        android:layout_alignParentEnd="true"
-        android:layout_below="@+id/search_container_all_apps"
-        android:layout_marginTop="-5dp"
-        android:layout_marginEnd="-45dp" />
-
-    <com.android.launcher3.allapps.LandscapeFastScroller
-        android:id="@+id/fast_scroller"
-        android:layout_width="48dp"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
-        android:layout_alignParentEnd="true"
-        android:layout_below="@+id/search_container_all_apps"
-        android:layout_marginEnd="-88dp"
-        android:layout_marginTop="14dp"
-        launcher:canThumbDetach="true" />
-
-</merge>
\ No newline at end of file
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index 0a4fec1..9bd3c67 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -57,16 +57,13 @@
         <com.android.launcher3.views.AllAppsScrim
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:id="@+id/all_apps_scrim"
-            launcher:layout_ignoreInsets="true" />
+            android:id="@+id/all_apps_scrim" />
 
         <!-- DO NOT CHANGE THE ID -->
         <include layout="@layout/hotseat"
             android:id="@+id/hotseat"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_gravity="right"
-            launcher:layout_ignoreInsets="true" />
+            android:layout_height="match_parent" />
 
         <include
             android:id="@+id/drop_target_bar"
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index 33b350a..b678398 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -51,15 +51,13 @@
         <com.android.launcher3.views.AllAppsScrim
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:id="@+id/all_apps_scrim"
-            launcher:layout_ignoreInsets="true" />
+            android:id="@+id/all_apps_scrim" />
 
         <!-- DO NOT CHANGE THE ID -->
         <include layout="@layout/hotseat"
             android:id="@+id/hotseat"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            launcher:layout_ignoreInsets="true" />
+            android:layout_height="match_parent" />
 
         <!-- Keep these behind the workspace so that they are not visible when
              we go into AllApps -->
diff --git a/res/layout-sw720dp/all_apps_fast_scroller.xml b/res/layout-sw720dp/all_apps_fast_scroller.xml
deleted file mode 100644
index 5537bc6..0000000
--- a/res/layout-sw720dp/all_apps_fast_scroller.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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.
--->
-<merge
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <!-- Fast scroller popup -->
-    <TextView
-        android:id="@+id/fast_scroller_popup"
-        style="@style/FastScrollerPopup"
-        android:layout_alignParentEnd="true"
-        android:layout_below="@+id/search_container_all_apps"
-        android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
-
-    <com.android.launcher3.views.RecyclerViewFastScroller
-        android:id="@+id/fast_scroller"
-        android:layout_width="@dimen/fastscroll_width"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
-        android:layout_alignParentEnd="true"
-        android:layout_below="@+id/search_container_all_apps"
-        android:layout_marginEnd="@dimen/fastscroll_end_margin"
-        launcher:canThumbDetach="true" />
-
-</merge>
\ No newline at end of file
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index d7e931a..7e6c659 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -50,15 +50,13 @@
         <com.android.launcher3.views.AllAppsScrim
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:id="@+id/all_apps_scrim"
-            launcher:layout_ignoreInsets="true" />
+            android:id="@+id/all_apps_scrim" />
 
         <!-- DO NOT CHANGE THE ID -->
         <include layout="@layout/hotseat"
             android:id="@+id/hotseat"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            launcher:layout_ignoreInsets="true" />
+            android:layout_height="match_parent" />
 
         <include
             android:id="@+id/drop_target_bar"
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
deleted file mode 100644
index afa19b8..0000000
--- a/res/layout/user_folder.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<com.android.launcher3.folder.Folder xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:background="@drawable/round_rect_primary"
-    android:elevation="5dp"
-    android:orientation="vertical" >
-
-    <com.android.launcher3.folder.FolderPagedView
-        android:id="@+id/folder_content"
-        android:clipToPadding="false"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:paddingLeft="8dp"
-        android:paddingRight="8dp"
-        android:paddingTop="16dp"
-        launcher:pageIndicator="@+id/folder_page_indicator" />
-
-    <LinearLayout
-        android:id="@+id/folder_footer"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:clipChildren="false"
-        android:orientation="horizontal"
-        android:paddingLeft="12dp"
-        android:paddingRight="12dp" >
-
-        <com.android.launcher3.ExtendedEditText
-            android:id="@+id/folder_name"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:layout_weight="1"
-            android:background="@android:color/transparent"
-            android:fontFamily="sans-serif-condensed"
-            android:gravity="center_horizontal"
-            android:hint="@string/folder_hint_text"
-            android:imeOptions="flagNoExtractUi"
-            android:paddingBottom="@dimen/folder_label_padding_bottom"
-            android:paddingTop="@dimen/folder_label_padding_top"
-            android:singleLine="true"
-            android:textColor="?android:attr/textColorTertiary"
-            android:includeFontPadding="false"
-            android:textColorHighlight="?android:attr/colorControlHighlight"
-            android:textColorHint="?android:attr/textColorHint"
-            android:textSize="@dimen/folder_label_text_size" />
-
-        <com.android.launcher3.pageindicators.PageIndicatorDots
-            android:id="@+id/folder_page_indicator"
-            android:layout_gravity="center_vertical"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:elevation="1dp"
-            />
-
-    </LinearLayout>
-
-</com.android.launcher3.folder.Folder>
\ No newline at end of file
diff --git a/res/layout/work_tab_footer.xml b/res/layout/work_tab_footer.xml
index e3416ac..dc0fdd4 100644
--- a/res/layout/work_tab_footer.xml
+++ b/res/layout/work_tab_footer.xml
@@ -73,7 +73,6 @@
         android:lines="1"
         android:minHeight="24dp"
         android:paddingStart="12dp"
-        android:text="@string/managed_by_your_organisation"
         android:textColor="?android:attr/textColorHint"
         android:textSize="13sp"/>
 
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 8c833fa..35d57ff 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -134,4 +134,5 @@
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Arbeitsprofil"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Hier findest du Apps für die Arbeit"</string>
     <string name="bottom_work_tab_user_education_body" msgid="5834430249581360068">"Jede App für die Arbeit hat ein orangefarbenes Logo, um zu zeigen, dass sich deine Organisation um die Sicherheit der App kümmert. Diese Apps können auf deinen Startbildschirm verschoben werden, damit du leichter auf sie zugreifen kannst."</string>
+    <string name="managed_by_your_organisation" msgid="3989423660315876998">"Wird von deiner Organisation verwaltet"</string>
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 14e8e42..31dff6b 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -134,4 +134,5 @@
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"कार्य प्रोफ़ाइल"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"काम से जुड़े सभी ऐप्लिकेशन यहां पाएं"</string>
     <string name="bottom_work_tab_user_education_body" msgid="5834430249581360068">"काम से जुड़े हर ऐप्लिकेशन पर एक नारंगी बैज होता है. इसका मतलब यह है कि यह ऐप्लिकेशन आपके संगठन की ओर से सुरक्षित किया जाता है. आसानी से इस्तेमाल के लिए, काम से जुड़े ऐप्लिकेशन होम स्क्रीन पर ले जाए जा सकते हैं."</string>
+    <string name="managed_by_your_organisation" msgid="3989423660315876998">"आपके संगठन की ओर से प्रबंधित किया गया"</string>
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index c632e67..44a872c 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -134,4 +134,5 @@
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"פרופיל עבודה"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"ניתן למצוא כאן את אפליקציות העבודה"</string>
     <string name="bottom_work_tab_user_education_body" msgid="5834430249581360068">"לכל אפליקציית עבודה יש תג כתום, וזה אומר שאבטחתה מטופלת בידי הארגון. ניתן להעביר אפליקציות עבודה אל מסך דף הבית כדי להקל את הגישה אליהן."</string>
+    <string name="managed_by_your_organisation" msgid="3989423660315876998">"מנוהל בידי הארגון שלך"</string>
 </resources>
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index 92420a2..40ffffe 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -35,19 +35,8 @@
     <!-- Folders -->
     <dimen name="folder_preview_padding">2dp</dimen>
 
-    <!-- Page indicator -->
-    <dimen name="dynamic_grid_page_indicator_land_left_nav_bar_gutter_width">50dp</dimen>
-    <dimen name="dynamic_grid_page_indicator_land_right_nav_bar_gutter_width">74dp</dimen>
-
     <!-- Hotseat -->
     <!-- Will be set to equal the hotseat icon size. -->
     <dimen name="dynamic_grid_hotseat_size">0dp</dimen>
-
-    <dimen name="dynamic_grid_hotseat_land_left_nav_bar_gutter_width">50dp</dimen>
-    <dimen name="dynamic_grid_hotseat_land_left_nav_bar_left_padding">44dp</dimen>
-    <dimen name="dynamic_grid_hotseat_land_left_nav_bar_right_padding">18dp</dimen>
-
-    <dimen name="dynamic_grid_hotseat_land_right_nav_bar_gutter_width">56dp</dimen>
-    <dimen name="dynamic_grid_hotseat_land_right_nav_bar_left_padding">32dp</dimen>
-    <dimen name="dynamic_grid_hotseat_land_right_nav_bar_right_padding">6dp</dimen>
+    <dimen name="dynamic_grid_hotseat_side_padding">16dp</dimen>
 </resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 0ff0d75..2096200 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -47,10 +47,6 @@
     <!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
     <integer name="config_workspaceSpringLoadShrinkPercentage">90</integer>
 
-    <!-- This constant stores the ratio of the all apps button drawable which
-         is used for internal (baked-in) padding -->
-    <integer name="config_allAppsButtonPaddingPercent">17</integer>
-
     <!-- The duration of the animation from search hint to text entry -->
     <integer name="config_searchHintAnimationDuration">50</integer>
 
@@ -105,6 +101,9 @@
     <!-- Package name of the default wallpaper picker. -->
     <string name="wallpaper_picker_package" translatable="false"></string>
 
+    <!-- Whitelisted package to retrieve packagename for badge. Can be empty. -->
+    <string name="shortcutinfocompat_badgepkg_whitelist" translatable="false"></string>
+
     <!-- View ID to use for QSB widget -->
     <item type="id" name="qsb_widget" />
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index e10719b..f53fe79 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -22,8 +22,6 @@
     <dimen name="dynamic_grid_edge_margin">8dp</dimen>
     <dimen name="dynamic_grid_min_page_indicator_size">24dp</dimen>
     <dimen name="dynamic_grid_page_indicator_line_height">1dp</dimen>
-    <dimen name="dynamic_grid_page_indicator_land_left_nav_bar_gutter_width">0dp</dimen>
-    <dimen name="dynamic_grid_page_indicator_land_right_nav_bar_gutter_width">0dp</dimen>
     <dimen name="dynamic_grid_icon_drawable_padding">8dp</dimen>
     <dimen name="dynamic_grid_overview_min_icon_zone_height">80dp</dimen>
     <dimen name="dynamic_grid_overview_max_icon_zone_height">120dp</dimen>
@@ -42,13 +40,7 @@
     <dimen name="dynamic_grid_hotseat_top_padding">8dp</dimen>
     <dimen name="dynamic_grid_hotseat_bottom_padding">2dp</dimen>
     <dimen name="dynamic_grid_hotseat_size">80dp</dimen>
-
-    <dimen name="dynamic_grid_hotseat_land_left_nav_bar_right_padding">0dp</dimen>
-    <dimen name="dynamic_grid_hotseat_land_right_nav_bar_right_padding">0dp</dimen>
-    <dimen name="dynamic_grid_hotseat_land_left_nav_bar_gutter_width">0dp</dimen>
-    <dimen name="dynamic_grid_hotseat_land_right_nav_bar_gutter_width">0dp</dimen>
-    <dimen name="dynamic_grid_hotseat_land_left_nav_bar_left_padding">0dp</dimen>
-    <dimen name="dynamic_grid_hotseat_land_right_nav_bar_left_padding">0dp</dimen>
+    <dimen name="dynamic_grid_hotseat_side_padding">0dp</dimen>
 
     <!-- Hotseat/all-apps scrim -->
     <dimen name="all_apps_scrim_radius">10dp</dimen>
@@ -93,7 +85,6 @@
     <dimen name="all_apps_empty_search_bg_top_offset">144dp</dimen>
     <dimen name="all_apps_background_canvas_width">700dp</dimen>
     <dimen name="all_apps_background_canvas_height">475dp</dimen>
-    <dimen name="all_apps_caret_workspace_offset">18dp</dimen>
     <dimen name="all_apps_header_tab_height">50dp</dimen>
     <dimen name="all_apps_tabs_indicator_height">2dp</dimen>
     <dimen name="all_apps_header_top_padding">36dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d77065c..ee09946 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -324,13 +324,16 @@
     <!-- Label of tab to indicate work apps -->
     <string name="all_apps_work_tab">Work</string>
 
-    <!-- Label of the work mode toggle -->
+    <!-- This string is in the work profile tab when a user has All Apps open on their phone. This is a label for a toggle to turn the work profile on and off. "Work profile" means a separate profile on a user's phone that's specifically for their work apps and managed by their company. "Work" is used as an adjective.-->
     <string name="work_profile_toggle_label">Work profile</string>
-    <!-- Title in bottom user education view in work tab -->
+    <!-- Title of an overlay in All Apps. This overlay is letting a user know about their work profile, which is managed by their employer. "Work apps" are apps in a user's work profile.-->
     <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>
-    <!-- Label in work tab to tell users that work profile is managed by their organisation. -->
-    <string name="managed_by_your_organisation">Managed by your organisation</string>
+    <!-- Text in an overlay in All Apps. This overlay is letting a user know about their work profile, which is managed by their employer.-->
+    <string name="bottom_work_tab_user_education_body">Each work app has an orange badge and is kept secure by your organization. Move apps to your Home screen for easier access.</string>
+    <!-- This string is in the work profile tab when a user has All Apps open on their phone. It describes the label of a toggle, "Work profile," as being managed by the user's employer.
+    "Organization" is used to represent a variety of businesses, non-profits, and educational institutions).-->
+    <string name="work_mode_on_label">Managed by your organization</string>
+    <!-- This string appears under a the label of a toggle in the work profile tab on a user's phone. It describes the status of the toggle, "Work profile," when it's turned off. "Work profile" means a separate profile on a user's phone that's speficially for their work apps and is managed by their company.-->
+    <string name="work_mode_off_label">Notifications and apps are off</string>
 
 </resources>
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 9796d18..4d1bedc 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -110,7 +110,7 @@
         info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
                 ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
 
-        if (FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.ATLEAST_OREO
+        if (Utilities.ATLEAST_OREO
                 && appInfo.targetSdkVersion >= Build.VERSION_CODES.O
                 && Process.myUserHandle().equals(lai.getUser())) {
             // The icon for a non-primary user is badged, hence it's not exactly an adaptive icon.
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 9775955..469b8bb 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -437,9 +437,11 @@
             }
 
             // Auto installs should always support the current platform version.
+            LauncherIcons li = LauncherIcons.obtain(mContext);
             mValues.put(LauncherSettings.Favorites.ICON, Utilities.flattenBitmap(
-                    LauncherIcons.createBadgedIconBitmap(
-                            icon, Process.myUserHandle(), mContext, VERSION.SDK_INT).icon));
+                    li.createBadgedIconBitmap(icon, Process.myUserHandle(), VERSION.SDK_INT).icon));
+            li.recycle();
+
             mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId));
             mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId));
 
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index e496495..a54c21c 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -31,6 +31,8 @@
     protected UserEventDispatcher mUserEventDispatcher;
     protected SystemUiController mSystemUiController;
 
+    private boolean mStarted;
+
     public DeviceProfile getDeviceProfile() {
         return mDeviceProfile;
     }
@@ -69,4 +71,20 @@
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
     }
+
+    @Override
+    protected void onStart() {
+        mStarted = true;
+        super.onStart();
+    }
+
+    @Override
+    protected void onStop() {
+        mStarted = false;
+        super.onStop();
+    }
+
+    public boolean isStarted() {
+        return mStarted;
+    }
 }
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 76c7845..cc13263 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -91,7 +91,7 @@
         // Move to mScrollbar's coordinate system.
         // We need to take parent into account (view pager's location)
         ViewGroup parent = (ViewGroup) getParent();
-        int left = parent.getLeft() + getLeft() - mScrollbar.getLeft();
+        int left = parent.getLeft() - mScrollbar.getLeft();
         int top = parent.getTop() + getTop() - mScrollbar.getTop() - getScrollBarTop();
         ev.offsetLocation(left, top);
         try {
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 4087df1..5e4f670 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -847,10 +847,6 @@
         return super.verifyDrawable(who) || (who == mBackground);
     }
 
-    public void setShortcutAndWidgetAlpha(float alpha) {
-        mShortcutsAndWidgets.setAlpha(alpha);
-    }
-
     public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
         return mShortcutsAndWidgets;
     }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 3bfc41c..164efe5 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -25,24 +25,12 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.util.DisplayMetrics;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.FrameLayout;
 
 import com.android.launcher3.CellLayout.ContainerType;
-import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.badge.BadgeRenderer;
-import com.android.launcher3.views.AllAppsScrim;
-
-import java.util.ArrayList;
 
 public class DeviceProfile {
 
-    public interface LauncherLayoutChangeListener {
-        void onLauncherLayoutChanged();
-    }
-
     public final InvariantDeviceProfile inv;
 
     // Device properties
@@ -66,23 +54,19 @@
 
     private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f;
 
-
     // Workspace
-    private final int desiredWorkspaceLeftRightMarginPx;
+    public final int desiredWorkspaceLeftRightMarginPx;
     public final int cellLayoutPaddingLeftRightPx;
     public final int cellLayoutBottomPaddingPx;
     public final int edgeMarginPx;
     public final Rect defaultWidgetPadding;
-    private final int defaultPageSpacingPx;
+    public final int defaultPageSpacingPx;
     private final int topWorkspacePadding;
     public float workspaceSpringLoadShrinkFactor;
     public final int workspaceSpringLoadedBottomSpace;
 
     // Page indicator
-    private int pageIndicatorSizePx;
-    private final int pageIndicatorLandLeftNavBarGutterPx;
-    private final int pageIndicatorLandRightNavBarGutterPx;
-    private final int pageIndicatorLandWorkspaceOffsetPx;
+    public final int pageIndicatorSizePx;
 
     // Workspace icons
     public int iconSizePx;
@@ -112,20 +96,12 @@
     public int hotseatCellHeightPx;
     // In portrait: size = height, in landscape: size = width
     public int hotseatBarSizePx;
-    public int hotseatBarTopPaddingPx;
-    public int hotseatBarBottomPaddingPx;
-
-    public int hotseatBarLeftNavBarLeftPaddingPx;
-    public int hotseatBarLeftNavBarRightPaddingPx;
-
-    public int hotseatBarRightNavBarLeftPaddingPx;
-    public int hotseatBarRightNavBarRightPaddingPx;
+    public final int hotseatBarTopPaddingPx;
+    public final int hotseatBarBottomPaddingPx;
+    public final int hotseatBarSidePaddingPx;
 
     // All apps
     public int allAppsCellHeightPx;
-    public int allAppsNumCols;
-    public int allAppsNumPredictiveCols;
-    public int allAppsButtonVisualSize;
     public int allAppsIconSizePx;
     public int allAppsIconDrawablePaddingPx;
     public float allAppsIconTextSizePx;
@@ -137,10 +113,8 @@
     public int dropTargetBarSizePx;
 
     // Insets
-    private Rect mInsets = new Rect();
-
-    // Listeners
-    private ArrayList<LauncherLayoutChangeListener> mListeners = new ArrayList<>();
+    private final Rect mInsets = new Rect();
+    public final Rect workspacePadding = new Rect();
 
     // Icon badges
     public BadgeRenderer mBadgeRenderer;
@@ -181,12 +155,6 @@
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_bottom_padding);
         pageIndicatorSizePx = res.getDimensionPixelSize(
                 R.dimen.dynamic_grid_min_page_indicator_size);
-        pageIndicatorLandLeftNavBarGutterPx = res.getDimensionPixelSize(
-                R.dimen.dynamic_grid_page_indicator_land_left_nav_bar_gutter_width);
-        pageIndicatorLandRightNavBarGutterPx = res.getDimensionPixelSize(
-                R.dimen.dynamic_grid_page_indicator_land_right_nav_bar_gutter_width);
-        pageIndicatorLandWorkspaceOffsetPx =
-                res.getDimensionPixelSize(R.dimen.all_apps_caret_workspace_offset);
         defaultPageSpacingPx =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
         topWorkspacePadding =
@@ -203,14 +171,8 @@
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
         hotseatBarBottomPaddingPx =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
-        hotseatBarLeftNavBarRightPaddingPx = res.getDimensionPixelSize(
-                R.dimen.dynamic_grid_hotseat_land_left_nav_bar_right_padding);
-        hotseatBarRightNavBarRightPaddingPx = res.getDimensionPixelSize(
-                R.dimen.dynamic_grid_hotseat_land_right_nav_bar_right_padding);
-        hotseatBarLeftNavBarLeftPaddingPx = res.getDimensionPixelSize(
-                R.dimen.dynamic_grid_hotseat_land_left_nav_bar_left_padding);
-        hotseatBarRightNavBarLeftPaddingPx = res.getDimensionPixelSize(
-                R.dimen.dynamic_grid_hotseat_land_right_nav_bar_left_padding);
+        hotseatBarSidePaddingPx =
+                res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
         hotseatBarSizePx = isVerticalBarLayout()
                 ? Utilities.pxFromDp(inv.iconSize, dm)
                 : res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_size)
@@ -244,13 +206,17 @@
             // Recalculate the available dimensions using the new hotseat size.
             updateAvailableDimensions(dm, res);
         }
-
-        computeAllAppsButtonSize(context);
+        updateWorkspacePadding();
 
         // This is done last, after iconSizePx is calculated above.
         mBadgeRenderer = new BadgeRenderer(iconSizePx);
     }
 
+    public DeviceProfile copy(Context context) {
+        Point size = new Point(availableWidthPx, availableHeightPx);
+        return new DeviceProfile(context, inv, size, size, widthPx, heightPx, isLandscape);
+    }
+
     DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
         // We take the minimum sizes of this profile and it's multi-window variant to ensure that
         // the system decor is always excluded.
@@ -274,22 +240,11 @@
         float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
         float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
         profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
+        profile.updateWorkspacePadding();
 
         return profile;
     }
 
-    public void addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) {
-        if (!mListeners.contains(listener)) {
-            mListeners.add(listener);
-        }
-    }
-
-    public void removeLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) {
-        if (mListeners.contains(listener)) {
-            mListeners.remove(listener);
-        }
-    }
-
     /**
      * Adjusts the profile so that the labels on the Workspace are hidden.
      * It is important to call this method after the All Apps variables have been set.
@@ -308,17 +263,6 @@
                 + topBottomPadding * 2;
     }
 
-    /**
-     * Determine the exact visual footprint of the all apps button, taking into account scaling
-     * and internal padding of the drawable.
-     */
-    private void computeAllAppsButtonSize(Context context) {
-        Resources res = context.getResources();
-        float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
-        allAppsButtonVisualSize = (int) (iconSizePx * (1 - padding)) - context.getResources()
-                        .getDimensionPixelSize(R.dimen.all_apps_button_scale_down);
-    }
-
     private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
         updateIconSize(1f, res, dm);
 
@@ -334,7 +278,8 @@
 
     private void updateIconSize(float scale, Resources res, DisplayMetrics dm) {
         // Workspace
-        float invIconSizePx = isVerticalBarLayout() ? inv.landscapeIconSize : inv.iconSize;
+        final boolean isVerticalLayout = isVerticalBarLayout();
+        float invIconSizePx = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
         iconSizePx = (int) (Utilities.pxFromDp(invIconSizePx, dm) * scale);
         iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
         iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
@@ -342,7 +287,7 @@
         cellHeightPx = iconSizePx + iconDrawablePaddingPx
                 + Utilities.calculateTextHeight(iconTextSizePx);
         int cellYPadding = (getCellSize().y - cellHeightPx) / 2;
-        if (iconDrawablePaddingPx > cellYPadding && !isVerticalBarLayout()
+        if (iconDrawablePaddingPx > cellYPadding && !isVerticalLayout
                 && !inMultiWindowMode()) {
             // Ensures that the label is closer to its corresponding icon. This is not an issue
             // with vertical bar layout or multi-window mode since the issue is handled separately
@@ -358,18 +303,18 @@
         allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
         allAppsCellHeightPx = getCellSize().y;
 
-        if (isVerticalBarLayout()) {
+        if (isVerticalLayout) {
             // Always hide the Workspace text with vertical bar layout.
             adjustToHideWorkspaceLabels();
         }
 
         // Hotseat
-        if (isVerticalBarLayout()) {
+        if (isVerticalLayout) {
             hotseatBarSizePx = iconSizePx;
         }
         hotseatCellHeightPx = iconSizePx;
 
-        if (!isVerticalBarLayout()) {
+        if (!isVerticalLayout) {
             int expectedWorkspaceHeight = availableHeightPx - hotseatBarSizePx
                     - pageIndicatorSizePx - topWorkspacePadding;
             float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace;
@@ -396,15 +341,16 @@
 
         // Don't let the folder get too close to the edges of the screen.
         int folderMargin = edgeMarginPx;
+        Point totalWorkspacePadding = getTotalWorkspacePadding();
 
         // Check if the icons fit within the available height.
         float usedHeight = folderCellHeightPx * inv.numFolderRows + folderBottomPanelSize;
-        int maxHeight = availableHeightPx - getTotalWorkspacePadding().y - folderMargin;
+        int maxHeight = availableHeightPx - totalWorkspacePadding.y - folderMargin;
         float scaleY = maxHeight / usedHeight;
 
         // Check if the icons fit within the available width.
         float usedWidth = folderCellWidthPx * inv.numFolderColumns;
-        int maxWidth = availableWidthPx - getTotalWorkspacePadding().x - folderMargin;
+        int maxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin;
         float scaleX = maxWidth / usedWidth;
 
         float scale = Math.min(scaleX, scaleY);
@@ -430,32 +376,11 @@
 
     public void updateInsets(Rect insets) {
         mInsets.set(insets);
+        updateWorkspacePadding();
     }
 
-    public void updateAppsViewNumCols() {
-        allAppsNumCols = allAppsNumPredictiveCols = inv.numColumns;
-    }
-
-    /** Returns the width and height of the search bar, ignoring any padding. */
-    public Point getSearchBarDimensForWidgetOpts() {
-        if (isVerticalBarLayout()) {
-            return new Point(dropTargetBarSizePx, availableHeightPx - 2 * edgeMarginPx);
-        } else {
-            int gap;
-            if (isTablet) {
-                // Pad the left and right of the workspace to ensure consistent spacing
-                // between all icons
-                int width = getCurrentWidth();
-                // XXX: If the icon size changes across orientations, we will have to take
-                //      that into account here too.
-                gap = ((width - 2 * edgeMarginPx
-                        - (inv.numColumns * cellWidthPx)) / (2 * (inv.numColumns + 1)))
-                        + edgeMarginPx;
-            } else {
-                gap = desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right;
-            }
-            return new Point(availableWidthPx - 2 * gap, dropTargetBarSizePx);
-        }
+    public Rect getInsets() {
+        return mInsets;
     }
 
     public Point getCellSize() {
@@ -471,43 +396,40 @@
     }
 
     public Point getTotalWorkspacePadding() {
-        Rect padding = getWorkspacePadding(null);
-        return new Point(padding.left + padding.right, padding.top + padding.bottom);
+        updateWorkspacePadding();
+        return new Point(workspacePadding.left + workspacePadding.right,
+                workspacePadding.top + workspacePadding.bottom);
     }
 
     /**
-     * Returns the workspace padding in the specified orientation.
+     * Updates {@link #workspacePadding} as a result of any internal value change to reflect the
+     * new workspace padding
      */
-    public Rect getWorkspacePadding(Rect recycle) {
-        Rect padding = recycle == null ? new Rect() : recycle;
+    private void updateWorkspacePadding() {
+        Rect padding = workspacePadding;
         if (isVerticalBarLayout()) {
-            if (mInsets.left > 0) {
-                padding.set(mInsets.left + pageIndicatorLandLeftNavBarGutterPx,
-                        0,
-                        hotseatBarSizePx + hotseatBarLeftNavBarRightPaddingPx
-                                + hotseatBarLeftNavBarLeftPaddingPx
-                                - mInsets.left,
-                        edgeMarginPx);
+            padding.top = 0;
+            padding.bottom = edgeMarginPx;
+            padding.left = hotseatBarSidePaddingPx;
+            padding.right = hotseatBarSidePaddingPx;
+            if (mInsets.left > mInsets.right) {
+                padding.left += hotseatBarSizePx;
+                padding.right += pageIndicatorSizePx;
             } else {
-                padding.set(pageIndicatorLandRightNavBarGutterPx,
-                        0,
-                        hotseatBarSizePx + hotseatBarRightNavBarRightPaddingPx
-                                + hotseatBarRightNavBarLeftPaddingPx,
-                        edgeMarginPx);
+                padding.left += pageIndicatorSizePx;
+                padding.right += hotseatBarSizePx;
             }
         } else {
             int paddingBottom = hotseatBarSizePx + pageIndicatorSizePx;
             if (isTablet) {
                 // Pad the left and right of the workspace to ensure consistent spacing
                 // between all icons
-                int width = getCurrentWidth();
-                int height = getCurrentHeight();
                 // The amount of screen space available for left/right padding.
-                int availablePaddingX = Math.max(0, width - ((inv.numColumns * cellWidthPx) +
+                int availablePaddingX = Math.max(0, widthPx - ((inv.numColumns * cellWidthPx) +
                         ((inv.numColumns - 1) * cellWidthPx)));
                 availablePaddingX = (int) Math.min(availablePaddingX,
-                            width * MAX_HORIZONTAL_PADDING_PERCENT);
-                int availablePaddingY = Math.max(0, height - topWorkspacePadding - paddingBottom
+                        widthPx * MAX_HORIZONTAL_PADDING_PERCENT);
+                int availablePaddingY = Math.max(0, heightPx - topWorkspacePadding - paddingBottom
                         - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx
                         - hotseatBarBottomPaddingPx);
                 padding.set(availablePaddingX / 2, topWorkspacePadding + availablePaddingY / 2,
@@ -520,7 +442,6 @@
                         paddingBottom);
             }
         }
-        return padding;
     }
 
     /**
@@ -535,26 +456,14 @@
                     mInsets.top + availableHeightPx);
         } else {
             // Folders should only appear below the drop target bar and above the hotseat
-            return new Rect(mInsets.left,
+            return new Rect(mInsets.left + edgeMarginPx,
                     mInsets.top + dropTargetBarSizePx + edgeMarginPx,
-                    mInsets.left + availableWidthPx,
+                    mInsets.left + availableWidthPx - edgeMarginPx,
                     mInsets.top + availableHeightPx - hotseatBarSizePx
                             - pageIndicatorSizePx - edgeMarginPx);
         }
     }
 
-    private int getWorkspacePageSpacing() {
-        if (isVerticalBarLayout() || isLargeTablet) {
-            // In landscape mode the page spacing is set to the default.
-            return defaultPageSpacingPx;
-        } else {
-            // In portrait, we want the pages spaced such that there is no
-            // overhang of the previous / next page into the current page viewport.
-            // We assume symmetrical padding in portrait mode.
-            return Math.max(defaultPageSpacingPx, getWorkspacePadding(null).left + 1);
-        }
-    }
-
     public static int calculateCellWidth(int width, int countX) {
         return width / countX;
     }
@@ -571,136 +480,10 @@
         return isLandscape && transposeLayoutWithOrientation;
     }
 
-    boolean shouldFadeAdjacentWorkspaceScreens() {
+    public boolean shouldFadeAdjacentWorkspaceScreens() {
         return isVerticalBarLayout() || isLargeTablet;
     }
 
-    public void layout(Launcher launcher, boolean notifyListeners) {
-        FrameLayout.LayoutParams lp;
-        boolean hasVerticalBarLayout = isVerticalBarLayout();
-
-        // Layout the search bar space
-        Point searchBarBounds = getSearchBarDimensForWidgetOpts();
-        View searchBar = launcher.getDropTargetBar();
-        lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
-        lp.width = searchBarBounds.x;
-        lp.height = searchBarBounds.y;
-        lp.topMargin = mInsets.top + edgeMarginPx;
-        searchBar.setLayoutParams(lp);
-
-        // Layout the workspace
-        PagedView workspace = launcher.getWorkspace();
-        Rect workspacePadding = getWorkspacePadding(null);
-        workspace.setPadding(workspacePadding.left, workspacePadding.top, workspacePadding.right,
-                workspacePadding.bottom);
-        workspace.setPageSpacing(getWorkspacePageSpacing());
-
-        AllAppsScrim allAppsScrim = launcher.findViewById(R.id.all_apps_scrim);
-
-        // Layout the hotseat
-        Hotseat hotseat = launcher.getHotseat();
-        lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
-        // We want the edges of the hotseat to line up with the edges of the workspace, but the
-        // icons in the hotseat are a different size, and so don't line up perfectly. To account for
-        // this, we pad the left and right of the hotseat with half of the difference of a workspace
-        // cell vs a hotseat cell.
-        float workspaceCellWidth = (float) getCurrentWidth() / inv.numColumns;
-        float hotseatCellWidth = (float) getCurrentWidth() / inv.numHotseatIcons;
-        int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
-        float scrimMargin = launcher.getResources().getDimension(R.dimen.all_apps_scrim_margin);
-
-        if (hasVerticalBarLayout) {
-            // Vertical hotseat -- The hotseat is fixed in the layout to be on the right of the
-            //                     screen regardless of RTL
-            int paddingRight = mInsets.left > 0
-                    ? hotseatBarLeftNavBarRightPaddingPx
-                    : hotseatBarRightNavBarRightPaddingPx;
-            int paddingLeft = mInsets.left > 0
-                    ? hotseatBarLeftNavBarLeftPaddingPx
-                    : hotseatBarRightNavBarLeftPaddingPx;
-
-            lp.gravity = Gravity.RIGHT;
-            lp.width = hotseatBarSizePx + mInsets.left + mInsets.right
-                    + paddingLeft + paddingRight;
-            lp.height = LayoutParams.MATCH_PARENT;
-
-            hotseat.getLayout().setPadding(mInsets.left + cellLayoutPaddingLeftRightPx
-                            + paddingLeft,
-                    mInsets.top,
-                    mInsets.right + cellLayoutPaddingLeftRightPx + paddingRight,
-                    workspacePadding.bottom + cellLayoutBottomPaddingPx);
-        } else if (isTablet) {
-            // Pad the hotseat with the workspace padding calculated above
-            lp.gravity = Gravity.BOTTOM;
-            lp.width = LayoutParams.MATCH_PARENT;
-            lp.height = hotseatBarSizePx + mInsets.bottom;
-            allAppsScrim.setDrawRegion(lp.height + scrimMargin);
-
-            hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left
-                            + cellLayoutPaddingLeftRightPx,
-                    hotseatBarTopPaddingPx,
-                    hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx,
-                    hotseatBarBottomPaddingPx + mInsets.bottom + cellLayoutBottomPaddingPx);
-        } else {
-            // For phones, layout the hotseat without any bottom margin
-            // to ensure that we have space for the folders
-            lp.gravity = Gravity.BOTTOM;
-            lp.width = LayoutParams.MATCH_PARENT;
-            lp.height = hotseatBarSizePx + mInsets.bottom;
-            allAppsScrim.setDrawRegion(lp.height + scrimMargin);
-
-            hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left
-                            + cellLayoutPaddingLeftRightPx,
-                    hotseatBarTopPaddingPx,
-                    hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx,
-                    hotseatBarBottomPaddingPx + mInsets.bottom + cellLayoutBottomPaddingPx);
-        }
-        hotseat.setLayoutParams(lp);
-
-        // Layout the page indicators
-        View pageIndicator = launcher.findViewById(R.id.page_indicator);
-        if (pageIndicator != null) {
-            lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
-            if (isVerticalBarLayout()) {
-                if (mInsets.left > 0) {
-                    lp.leftMargin = mInsets.left;
-                } else {
-                    lp.leftMargin = pageIndicatorLandWorkspaceOffsetPx;
-                }
-                lp.bottomMargin = workspacePadding.bottom;
-            } else {
-                // Put the page indicators above the hotseat
-                lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
-                lp.height = pageIndicatorSizePx;
-                lp.bottomMargin = hotseatBarSizePx + mInsets.bottom;
-            }
-            pageIndicator.setLayoutParams(lp);
-        }
-
-        // Layout the AllAppsRecyclerView
-        AllAppsContainerView appsView = launcher.findViewById(R.id.apps_view);
-        int paddingLeftRight = desiredWorkspaceLeftRightMarginPx + cellLayoutPaddingLeftRightPx;
-        appsView.setRecyclerViewSidePadding(paddingLeftRight, paddingLeftRight);
-
-        if (notifyListeners) {
-            for (int i = mListeners.size() - 1; i >= 0; i--) {
-                mListeners.get(i).onLauncherLayoutChanged();
-            }
-        }
-    }
-
-    public int getCurrentWidth() {
-        return isLandscape
-                ? Math.max(widthPx, heightPx)
-                : Math.min(widthPx, heightPx);
-    }
-
-    public int getCurrentHeight() {
-        return isLandscape
-                ? Math.min(widthPx, heightPx)
-                : Math.max(widthPx, heightPx);
-    }
-
     public int getCellHeight(@ContainerType int containerType) {
         switch (containerType) {
             case CellLayout.WORKSPACE:
@@ -715,20 +498,6 @@
         }
     }
 
-    /**
-     * @return the left/right paddings for all containers.
-     */
-    public final int[] getContainerPadding() {
-        // No paddings for portrait phone
-        if (isPhone && !isVerticalBarLayout()) {
-            return new int[] {0, 0};
-        }
-
-        // In landscape, we match the width of the workspace
-        Rect padding = getWorkspacePadding(null);
-        return new int[] { padding.left - mInsets.left, padding.right + mInsets.left};
-    }
-
     public boolean inMultiWindowMode() {
         return this != inv.landscapeProfile && this != inv.portraitProfile;
     }
@@ -743,6 +512,5 @@
         Configuration context = new Configuration(c.getResources().getConfiguration());
         context.orientation = orientation;
         return c.createConfigurationContext(context);
-
     }
 }
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index 106671f..494aae4 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -21,15 +21,19 @@
 
 import android.animation.TimeInterpolator;
 import android.content.Context;
+import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
+import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragController.DragListener;
 import com.android.launcher3.dragndrop.DragOptions;
 
 import java.util.ArrayList;
@@ -37,18 +41,14 @@
 /*
  * The top bar containing various drop targets: Delete/App Info/Uninstall.
  */
-public class DropTargetBar extends LinearLayout implements DragController.DragListener {
+public class DropTargetBar extends LinearLayout
+        implements DragListener, Insettable {
 
     protected static final int DEFAULT_DRAG_FADE_DURATION = 175;
     protected static final TimeInterpolator DEFAULT_INTERPOLATOR = Interpolators.ACCEL;
 
-    private final Runnable mFadeAnimationEndRunnable = new Runnable() {
-
-        @Override
-        public void run() {
-            updateVisibility(DropTargetBar.this, isAccessibilityEnabled(getContext()));
-        }
-    };
+    private final Runnable mFadeAnimationEndRunnable =
+            () -> updateVisibility(DropTargetBar.this, isAccessibilityEnabled(getContext()));
 
     @ViewDebug.ExportedProperty(category = "launcher")
     protected boolean mDeferOnDragEnd;
@@ -75,6 +75,40 @@
         setAlpha(0f);
     }
 
+    @Override
+    public void setInsets(Rect insets) {
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+
+        lp.leftMargin = insets.left;
+        lp.topMargin = insets.top;
+        lp.bottomMargin = insets.bottom;
+        lp.rightMargin = insets.right;
+
+        if (grid.isVerticalBarLayout()) {
+            lp.width = grid.dropTargetBarSizePx;
+            lp.height = grid.availableHeightPx - 2 * grid.edgeMarginPx;
+            lp.gravity = insets.left > insets.right ? Gravity.RIGHT : Gravity.LEFT;
+        } else {
+            int gap;
+            if (grid.isTablet) {
+                // XXX: If the icon size changes across orientations, we will have to take
+                //      that into account here too.
+                gap = ((grid.widthPx - 2 * grid.edgeMarginPx
+                        - (grid.inv.numColumns * grid.cellWidthPx))
+                        / (2 * (grid.inv.numColumns + 1)))
+                        + grid.edgeMarginPx;
+            } else {
+                gap = grid.desiredWorkspaceLeftRightMarginPx - grid.defaultWidgetPadding.right;
+            }
+            lp.width = grid.availableWidthPx - 2 * gap;
+
+            lp.topMargin += grid.edgeMarginPx;
+            lp.height = grid.dropTargetBarSizePx;
+        }
+        setLayoutParams(lp);
+    }
+
     public void setup(DragController dragController) {
         dragController.addDragListener(this);
         ArrayList<ButtonDropTarget> outList = new ArrayList<>();
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 20a6be2..25eacb5 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -17,35 +17,36 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
 
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 
-public class Hotseat extends FrameLayout
-        implements UserEventDispatcher.LogContainerProvider {
+public class Hotseat extends FrameLayout implements LogContainerProvider, Insettable {
 
+    private final Launcher mLauncher;
     private CellLayout mContent;
 
-    private Launcher mLauncher;
-
     @ViewDebug.ExportedProperty(category = "launcher")
-    private final boolean mHasVerticalHotseat;
+    private boolean mHasVerticalHotseat;
 
     public Hotseat(Context context) {
         this(context, null);
@@ -58,7 +59,6 @@
     public Hotseat(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         mLauncher = Launcher.getLauncher(context);
-        mHasVerticalHotseat = mLauncher.getDeviceProfile().isVerticalBarLayout();
     }
 
     public CellLayout getLayout() {
@@ -66,13 +66,6 @@
     }
 
     /**
-     * Returns whether there are other icons than the all apps button in the hotseat.
-     */
-    public boolean hasIcons() {
-        return mContent.getShortcutsAndWidgets().getChildCount() > 1;
-    }
-
-    /**
      * Registers the specified listener on the cell layout of the hotseat.
      */
     @Override
@@ -97,13 +90,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        mContent = (CellLayout) findViewById(R.id.layout);
-        if (grid.isVerticalBarLayout()) {
-            mContent.setGridSize(1, grid.inv.numHotseatIcons);
-        } else {
-            mContent.setGridSize(grid.inv.numHotseatIcons, 1);
-        }
+        mContent = findViewById(R.id.layout);
 
         resetLayout();
     }
@@ -166,4 +153,52 @@
         target.gridY = info.cellY;
         targetParent.containerType = ContainerType.HOTSEAT;
     }
+
+    @Override
+    public void setInsets(Rect insets) {
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        mHasVerticalHotseat = mLauncher.getDeviceProfile().isVerticalBarLayout();
+
+        if (mHasVerticalHotseat) {
+            mContent.setGridSize(1, grid.inv.numHotseatIcons);
+
+            lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
+            if (insets.left > insets.right) {
+                lp.gravity = Gravity.LEFT;
+                lp.width = grid.hotseatBarSizePx + insets.left + grid.hotseatBarSidePaddingPx;
+                getLayout().setPadding(
+                        insets.left, insets.top, grid.hotseatBarSidePaddingPx, insets.bottom);
+
+            } else {
+                lp.gravity = Gravity.RIGHT;
+                lp.width = grid.hotseatBarSizePx + insets.right + grid.hotseatBarSidePaddingPx;
+                getLayout().setPadding(
+                        grid.hotseatBarSidePaddingPx, insets.top, insets.right, insets.bottom);
+            }
+        } else {
+            mContent.setGridSize(grid.inv.numHotseatIcons, 1);
+
+            lp.gravity = Gravity.BOTTOM;
+            lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
+            lp.height = grid.hotseatBarSizePx + insets.bottom;
+
+            // We want the edges of the hotseat to line up with the edges of the workspace, but the
+            // icons in the hotseat are a different size, and so don't line up perfectly. To account for
+            // this, we pad the left and right of the hotseat with half of the difference of a workspace
+            // cell vs a hotseat cell.
+            float workspaceCellWidth = (float) grid.widthPx / grid.inv.numColumns;
+            float hotseatCellWidth = (float) grid.widthPx / grid.inv.numHotseatIcons;
+            int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
+            Rect workspacePadding = grid.workspacePadding;
+
+            getLayout().setPadding(
+                    hotseatAdjustment + workspacePadding.left + grid.cellLayoutPaddingLeftRightPx,
+                    grid.hotseatBarTopPaddingPx,
+                    hotseatAdjustment + workspacePadding.right + grid.cellLayoutPaddingLeftRightPx,
+                    grid.hotseatBarBottomPaddingPx + insets.bottom + grid.cellLayoutBottomPaddingPx);
+        }
+        setLayoutParams(lp);
+        InsettableFrameLayout.dispatchInsets(this, insets);
+    }
 }
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index baa60b0..a5ca3ee 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -193,8 +193,10 @@
     }
 
     protected BitmapInfo makeDefaultIcon(UserHandle user) {
-        Drawable unbadged = getFullResDefaultActivityIcon();
-        return LauncherIcons.createBadgedIconBitmap(unbadged, user, mContext, VERSION.SDK_INT);
+        try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
+            return li.createBadgedIconBitmap(
+                    getFullResDefaultActivityIcon(), user, VERSION.SDK_INT);
+        }
     }
 
     /**
@@ -378,8 +380,10 @@
         }
         if (entry == null) {
             entry = new CacheEntry();
-            LauncherIcons.createBadgedIconBitmap(getFullResIcon(app), app.getUser(),
-                    mContext,  app.getApplicationInfo().targetSdkVersion).applyTo(entry);
+            LauncherIcons li = LauncherIcons.obtain(mContext);
+            li.createBadgedIconBitmap(getFullResIcon(app), app.getUser(),
+                    app.getApplicationInfo().targetSdkVersion).applyTo(entry);
+            li.recycle();
         }
         entry.title = app.getLabel();
         entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
@@ -535,9 +539,10 @@
                 providerFetchedOnce = true;
 
                 if (info != null) {
-                    LauncherIcons.createBadgedIconBitmap(
-                            getFullResIcon(info), info.getUser(), mContext,
+                    LauncherIcons li = LauncherIcons.obtain(mContext);
+                    li.createBadgedIconBitmap(getFullResIcon(info), info.getUser(),
                             info.getApplicationInfo().targetSdkVersion).applyTo(entry);
+                    li.recycle();
                 } else {
                     if (usePackageIcon) {
                         CacheEntry packageEntry = getEntryForPackageLocked(
@@ -596,7 +601,9 @@
             entry.title = title;
         }
         if (icon != null) {
-            LauncherIcons.createIconBitmap(icon, mContext).applyTo(entry);
+            LauncherIcons li = LauncherIcons.obtain(mContext);
+            li.createIconBitmap(icon).applyTo(entry);
+            li.recycle();
         }
         if (!TextUtils.isEmpty(title) && entry.icon != null) {
             mCache.put(cacheKey, entry);
@@ -633,14 +640,17 @@
                         throw new NameNotFoundException("ApplicationInfo is null");
                     }
 
+                    LauncherIcons li = LauncherIcons.obtain(mContext);
                     // Load the full res icon for the application, but if useLowResIcon is set, then
                     // only keep the low resolution icon instead of the larger full-sized icon
-                    BitmapInfo iconInfo = LauncherIcons.createBadgedIconBitmap(
-                            appInfo.loadIcon(mPackageManager), user, mContext, appInfo.targetSdkVersion);
+                    BitmapInfo iconInfo = li.createBadgedIconBitmap(
+                            appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion);
                     if (mInstantAppResolver.isInstantApp(appInfo)) {
-                        LauncherIcons.badgeWithDrawable(iconInfo.icon,
-                                mContext.getDrawable(R.drawable.ic_instant_app_badge), mContext);
+                        li.badgeWithDrawable(iconInfo.icon,
+                                mContext.getDrawable(R.drawable.ic_instant_app_badge));
                     }
+                    li.recycle();
+
                     Bitmap lowResIcon =  generateLowResIcon(iconInfo.icon);
                     entry.title = appInfo.loadLabel(mPackageManager);
                     entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
@@ -776,10 +786,7 @@
     }
 
     private static final class IconDB extends SQLiteCacheHelper {
-        private final static int DB_VERSION = 18;
-
-        private final static int RELEASE_VERSION = DB_VERSION +
-                (FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? 0 : 1);
+        private final static int RELEASE_VERSION = 20;
 
         private final static String TABLE_NAME = "icons";
         private final static String COLUMN_ROWID = "rowid";
diff --git a/src/com/android/launcher3/InsettableFrameLayout.java b/src/com/android/launcher3/InsettableFrameLayout.java
index 60f5ca2..baf3328 100644
--- a/src/com/android/launcher3/InsettableFrameLayout.java
+++ b/src/com/android/launcher3/InsettableFrameLayout.java
@@ -97,4 +97,14 @@
         super.onViewAdded(child);
         setFrameLayoutChildInsets(child, mInsets, new Rect());
     }
+
+    public static void dispatchInsets(ViewGroup parent, Rect insets) {
+        final int n = parent.getChildCount();
+        for (int i = 0; i < n; i++) {
+            final View child = parent.getChildAt(i);
+            if (child instanceof Insettable) {
+                ((Insettable) child).setInsets(insets);
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index c476421..fe8a841 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -498,7 +498,9 @@
                 return Pair.create((ItemInfo) si, (Object) activityInfo);
             } else if (shortcutInfo != null) {
                 ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext);
-                LauncherIcons.createShortcutIcon(shortcutInfo, mContext).applyTo(si);
+                LauncherIcons li = LauncherIcons.obtain(mContext);
+                li.createShortcutIcon(shortcutInfo).applyTo(si);
+                li.recycle();
                 return Pair.create((ItemInfo) si, (Object) shortcutInfo);
             } else if (providerInfo != null) {
                 LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
@@ -643,15 +645,18 @@
         info.user = Process.myUserHandle();
 
         BitmapInfo iconInfo = null;
+        LauncherIcons li = LauncherIcons.obtain(app.getContext());
         if (bitmap instanceof Bitmap) {
-            iconInfo = LauncherIcons.createIconBitmap((Bitmap) bitmap, app.getContext());
+            iconInfo = li.createIconBitmap((Bitmap) bitmap);
         } else {
             Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
             if (extra instanceof Intent.ShortcutIconResource) {
                 info.iconResource = (Intent.ShortcutIconResource) extra;
-                iconInfo = LauncherIcons.createIconBitmap(info.iconResource, app.getContext());
+                iconInfo = li.createIconBitmap(info.iconResource);
             }
         }
+        li.recycle();
+
         if (iconInfo == null) {
             iconInfo = app.getIconCache().getDefaultIcon(info.user);
         }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index adfb44c..e3682b4 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -337,8 +337,6 @@
         mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
 
         setupViews();
-        mDeviceProfile.layout(this, false /* notifyListeners */);
-
         mPopupDataProvider = new PopupDataProvider(this);
 
         mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
@@ -392,6 +390,7 @@
                 ? SCREEN_ORIENTATION_UNSPECIFIED : SCREEN_ORIENTATION_NOSENSOR);
 
         setContentView(mLauncherView);
+        ((LauncherRootView) mLauncherView).dispatchInsets();
 
         // Listen for broadcasts
         IntentFilter filter = new IntentFilter();
@@ -441,11 +440,6 @@
 
     private LauncherCallbacks mLauncherCallbacks;
 
-    public void onInsetsChanged(Rect insets) {
-        mDeviceProfile.updateInsets(insets);
-        mDeviceProfile.layout(this, true /* notifyListeners */);
-    }
-
     /**
      * Call this after onCreate to set or clear overlay.
      */
@@ -921,7 +915,7 @@
      * Finds all the views we need and configure them properly.
      */
     private void setupViews() {
-        mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
+        mDragLayer = findViewById(R.id.drag_layer);
         mFocusHandler = mDragLayer.getFocusIndicatorHelper();
         mWorkspace = mDragLayer.findViewById(R.id.workspace);
         mWorkspace.initParentViews(mDragLayer);
@@ -1243,9 +1237,9 @@
                 }
 
                 // In all these cases, only animate if we're already on home
-                AbstractFloatingView.closeAllOpenViews(this, alreadyOnHome);
+                AbstractFloatingView.closeAllOpenViews(this, isStarted());
 
-                mStateManager.goToState(NORMAL, alreadyOnHome /* animated */);
+                mStateManager.goToState(NORMAL);
 
                 // Reset the apps view
                 if (!alreadyOnHome && mAppsView != null) {
@@ -1614,8 +1608,9 @@
         if (topView != null) {
             topView.onBackPressed();
         } else if (!isInState(NORMAL)) {
+            LauncherState lastState = mStateManager.getLastState();
             ued.logActionCommand(Action.Command.BACK, mStateManager.getState().containerType);
-            mStateManager.goToState(NORMAL);
+            mStateManager.goToState(lastState);
         } else {
             // Back button is a no-op here, but give at least some feedback for the button press
             mWorkspace.showOutlinesTemporarily();
@@ -2557,10 +2552,6 @@
         return bounceAnim;
     }
 
-    public boolean useVerticalBarLayout() {
-        return mDeviceProfile.isVerticalBarLayout();
-    }
-
     /**
      * Add the icons for all apps.
      *
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index ea4b280..1b169f5 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -620,7 +620,9 @@
             @Override
             public ShortcutInfo get() {
                 si.updateFromDeepShortcutInfo(info, mApp.getContext());
-                LauncherIcons.createShortcutIcon(info, mApp.getContext()).applyTo(si);
+                LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
+                li.createShortcutIcon(info).applyTo(si);
+                li.recycle();
                 return si;
             }
         });
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index a814323..1a1bec6 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -16,6 +16,8 @@
 
 public class LauncherRootView extends InsettableFrameLayout {
 
+    private final Launcher mLauncher;
+
     private final Paint mOpaquePaint;
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mDrawSideInsetBar;
@@ -32,6 +34,8 @@
         mOpaquePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
         mOpaquePaint.setColor(Color.BLACK);
         mOpaquePaint.setStyle(Paint.Style.FILL);
+
+        mLauncher = Launcher.getLauncher(context);
     }
 
     @Override
@@ -57,10 +61,11 @@
         } else {
             mLeftInsetBarWidth = mRightInsetBarWidth = 0;
         }
-        Launcher.getLauncher(getContext()).getSystemUiController().updateUiState(
+        mLauncher.getSystemUiController().updateUiState(
                 UI_STATE_ROOT_VIEW, mDrawSideInsetBar ? FLAG_DARK_NAV : 0);
 
-        boolean rawInsetsChanged = !mInsets.equals(insets);
+        // Update device profile before notifying th children.
+        mLauncher.getDeviceProfile().updateInsets(insets);
         setInsets(insets);
 
         if (mAlignedView != null) {
@@ -73,15 +78,13 @@
             }
         }
 
-        if (rawInsetsChanged) {
-            // Update the grid again
-            Launcher launcher = Launcher.getLauncher(getContext());
-            launcher.onInsetsChanged(insets);
-        }
-
         return true; // I'll take it from here
     }
 
+    public void dispatchInsets() {
+        fitSystemWindows(mInsets);
+    }
+
     @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index a1f5879..472a5a9 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.uioverrides.AllAppsState;
 import com.android.launcher3.states.SpringLoadedState;
 import com.android.launcher3.uioverrides.OverviewState;
+import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 import java.util.Arrays;
@@ -54,8 +55,11 @@
 
     private static final LauncherState[] sAllStates = new LauncherState[4];
 
+    /**
+     * TODO: Create a separate class for NORMAL state.
+     */
     public static final LauncherState NORMAL = new LauncherState(0, ContainerType.WORKSPACE,
-            0, 1f, FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED);
+            0, FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED);
 
     public static final LauncherState ALL_APPS = new AllAppsState(1);
 
@@ -95,13 +99,6 @@
     public final int transitionDuration;
 
     /**
-     * Fraction shift in the vertical translation UI and related properties
-     *
-     * @see com.android.launcher3.allapps.AllAppsTransitionController
-     */
-    public final float verticalProgress;
-
-    /**
      * True if the state allows workspace icons to be dragged.
      */
     public final boolean workspaceIconsCanBeDragged;
@@ -112,8 +109,7 @@
      */
     public final boolean disablePageClipping;
 
-    public LauncherState(int id, int containerType, int transitionDuration, float verticalProgress,
-            int flags) {
+    public LauncherState(int id, int containerType, int transitionDuration, int flags) {
         this.containerType = containerType;
         this.transitionDuration = transitionDuration;
 
@@ -126,8 +122,6 @@
         this.workspaceIconsCanBeDragged = (flags & FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED) != 0;
         this.disablePageClipping = (flags & FLAG_DISABLE_PAGE_CLIPPING) != 0;
 
-        this.verticalProgress = verticalProgress;
-
         this.ordinal = id;
         sAllStates[id] = this;
     }
@@ -154,6 +148,15 @@
         return launcher.getWorkspace();
     }
 
+    /**
+     * Fraction shift in the vertical translation UI and related properties
+     *
+     * @see com.android.launcher3.allapps.AllAppsTransitionController
+     */
+    public float getVerticalProgress(Launcher launcher) {
+        return 1f;
+    }
+
     public String getDescription(Launcher launcher) {
         return launcher.getWorkspace().getCurrentPageDescription();
     }
@@ -162,7 +165,7 @@
         if (this != NORMAL || !launcher.getDeviceProfile().shouldFadeAdjacentWorkspaceScreens()) {
             return DEFAULT_ALPHA_PROVIDER;
         }
-        final int centerPage = launcher.getWorkspace().getPageNearestToCenterOfScreen();
+        final int centerPage = launcher.getWorkspace().getNextPage();
         return new PageAlphaProvider(ACCEL_2) {
             @Override
             public float getPageAlpha(int pageIndex) {
@@ -171,6 +174,20 @@
         };
     }
 
+    public LauncherState getHistoryForState(LauncherState previousState) {
+        // No history is supported
+        return NORMAL;
+    }
+
+    /**
+     * Called when the start transition ends and the user settles on this particular state.
+     */
+    public void onStateTransitionEnd(Launcher launcher) {
+        if (this == NORMAL) {
+            UiFactory.resetOverview(launcher);
+        }
+    }
+
     protected static void dispatchWindowStateChanged(Launcher launcher) {
         launcher.getWindow().getDecorView().sendAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED);
     }
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index d25f958..bcb6252 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -66,7 +66,7 @@
  *          - Go back with back key  TODO: make this not go to workspace
  *          - From all apps
  *          - From workspace
- *   - Enter and exit car mode (becuase it causes an extra configuration changed)
+ *   - Enter and exit car mode (becase it causes an extra configuration changed)
  *          - From all apps
  *          - From the center workspace
  *          - From another workspace
@@ -82,6 +82,9 @@
     private StateHandler[] mStateHandlers;
     private LauncherState mState = NORMAL;
 
+    private LauncherState mLastStableState = NORMAL;
+    private LauncherState mCurrentStableState = NORMAL;
+
     private StateListener mStateListener;
 
     public LauncherStateManager(Launcher l) {
@@ -108,7 +111,7 @@
      * @see #goToState(LauncherState, boolean, Runnable)
      */
     public void goToState(LauncherState state) {
-        goToState(state, true, 0, null);
+        goToState(state, mLauncher.isStarted() /* animated */, 0, null);
     }
 
     /**
@@ -146,7 +149,6 @@
     private void goToState(LauncherState state, boolean animated, long delay,
             Runnable onCompleteRunnable) {
         if (mLauncher.isInState(state) && mConfig.mCurrentAnimation == null) {
-
             // Run any queued runnable
             if (onCompleteRunnable != null) {
                 onCompleteRunnable.run();
@@ -262,11 +264,22 @@
     }
 
     private void onStateTransitionEnd(LauncherState state) {
+        // Only change the stable states after the transitions have finished
+        if (state != mCurrentStableState) {
+            mLastStableState = state.getHistoryForState(mCurrentStableState);
+            mCurrentStableState = state;
+        }
+
+        state.onStateTransitionEnd(mLauncher);
         mLauncher.getWorkspace().setClipChildren(!state.disablePageClipping);
         mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
         mLauncher.finishAutoCancelActionMode();
     }
 
+    public LauncherState getLastState() {
+        return mLastStableState;
+    }
+
     /**
      * Cancels the current animation.
      */
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 87595d9..4c30853 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -155,10 +155,6 @@
     // Page Indicator
     @Thunk int mPageIndicatorViewId;
     protected PageIndicator mPageIndicator;
-    // The viewport whether the pages are to be contained (the actual view may be larger than the
-    // viewport)
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private Rect mViewport = new Rect();
 
     // Reordering
     // We use the min scale to determine how much to expand the actually PagedView measured
@@ -283,25 +279,6 @@
         }
     }
 
-    // Convenience methods to get the actual width/height of the PagedView (since it is measured
-    // to be larger to account for the minimum possible scale)
-    int getViewportWidth() {
-        return mViewport.width();
-    }
-    public int getViewportHeight() {
-        return mViewport.height();
-    }
-
-    // Convenience methods to get the offset ASSUMING that we are centering the pages in the
-    // PagedView both horizontally and vertically
-    int getViewportOffsetX() {
-        return (getMeasuredWidth() - getViewportWidth()) / 2;
-    }
-
-    int getViewportOffsetY() {
-        return (getMeasuredHeight() - getViewportHeight()) / 2;
-    }
-
     public PageIndicator getPageIndicator() {
         return mPageIndicator;
     }
@@ -587,12 +564,12 @@
     }
 
     public int getNormalChildHeight() {
-        return  getViewportHeight() - getPaddingTop() - getPaddingBottom()
+        return  getMeasuredHeight() - getPaddingTop() - getPaddingBottom()
                 - mInsets.top - mInsets.bottom;
     }
 
     public int getNormalChildWidth() {
-        return  getViewportWidth() - getPaddingLeft() - getPaddingRight()
+        return  getMeasuredWidth() - getPaddingLeft() - getPaddingRight()
                 - mInsets.left - mInsets.right;
     }
 
@@ -610,8 +587,6 @@
         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
 
-        mViewport.set(0, 0, widthSize, heightSize);
-
         if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
             return;
@@ -628,9 +603,9 @@
         if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
 
         int myWidthSpec = MeasureSpec.makeMeasureSpec(
-                getViewportWidth() - mInsets.left - mInsets.right, MeasureSpec.EXACTLY);
+                widthSize - mInsets.left - mInsets.right, MeasureSpec.EXACTLY);
         int myHeightSpec = MeasureSpec.makeMeasureSpec(
-                getViewportHeight() - mInsets.top - mInsets.bottom, MeasureSpec.EXACTLY);
+                heightSize - mInsets.top - mInsets.bottom, MeasureSpec.EXACTLY);
 
         // measureChildren takes accounts for content padding, we only need to care about extra
         // space due to insets.
@@ -648,19 +623,15 @@
         if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
         final int childCount = getChildCount();
 
-        int offsetX = getViewportOffsetX();
-        int offsetY = getViewportOffsetY();
-
-        // Update the viewport offsets
-        mViewport.offset(offsetX, offsetY);
-
         final int startIndex = mIsRtl ? childCount - 1 : 0;
         final int endIndex = mIsRtl ? -1 : childCount;
         final int delta = mIsRtl ? -1 : 1;
 
         int verticalPadding = getPaddingTop() + getPaddingBottom();
 
-        int childLeft = offsetX + getPaddingLeft();
+        int scrollOffsetLeft = mInsets.left + getPaddingLeft();
+        int childLeft = scrollOffsetLeft;
+
         if (mPageScrolls == null || childCount != mChildCountOnLastLayout) {
             mPageScrolls = new int[childCount];
         }
@@ -668,8 +639,8 @@
         for (int i = startIndex; i != endIndex; i += delta) {
             final View child = getPageAt(i);
             if (child.getVisibility() != View.GONE) {
-                int childTop = offsetY + getPaddingTop() + mInsets.top;
-                childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding
+                int childTop = getPaddingTop() + mInsets.top;
+                childTop += (getMeasuredHeight() - mInsets.top - mInsets.bottom - verticalPadding
                         - child.getMeasuredHeight()) / 2;
 
                 final int childWidth = child.getMeasuredWidth();
@@ -679,8 +650,7 @@
                 child.layout(childLeft, childTop,
                         childLeft + child.getMeasuredWidth(), childTop + childHeight);
 
-                int scrollOffsetLeft = getPaddingLeft();
-                mPageScrolls[i] = childLeft - scrollOffsetLeft - offsetX;
+                mPageScrolls[i] = childLeft - scrollOffsetLeft;
 
                 childLeft += childWidth + mPageSpacing + getChildGap();
             }
@@ -809,10 +779,7 @@
 
     protected int getChildOffset(int index) {
         if (index < 0 || index > getChildCount() - 1) return 0;
-
-        int offset = getPageAt(index).getLeft() - getViewportOffsetX();
-
-        return offset;
+        return getPageAt(index).getLeft();
     }
 
     @Override
@@ -930,32 +897,9 @@
         super.requestDisallowInterceptTouchEvent(disallowIntercept);
     }
 
-    /**
-     * Return true if a tap at (x, y) should trigger a flip to the previous page.
-     */
-    protected boolean hitsPreviousPage(float x, float y) {
-        if (mIsRtl) {
-            return (x > (getViewportOffsetX() + getViewportWidth() -
-                    getPaddingRight() - mPageSpacing));
-        }
-        return (x < getViewportOffsetX() + getPaddingLeft() + mPageSpacing);
-    }
-
-    /**
-     * Return true if a tap at (x, y) should trigger a flip to the next page.
-     */
-    protected boolean hitsNextPage(float x, float y) {
-        if (mIsRtl) {
-            return (x < getViewportOffsetX() + getPaddingLeft() + mPageSpacing);
-        }
-        return  (x > (getViewportOffsetX() + getViewportWidth() -
-                getPaddingRight() - mPageSpacing));
-    }
-
     /** Returns whether x and y originated within the buffered viewport */
     private boolean isTouchPointInViewportWithBuffer(int x, int y) {
-        sTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top,
-                mViewport.right + mViewport.width() / 2, mViewport.bottom);
+        sTmpRect.set(-getMeasuredWidth() / 2, 0, 3 * getMeasuredWidth() / 2, getMeasuredHeight());
         return sTmpRect.contains(x, y);
     }
 
@@ -1105,7 +1049,7 @@
     }
 
     protected float getScrollProgress(int screenCenter, View v, int page) {
-        final int halfScreenSize = getViewportWidth() / 2;
+        final int halfScreenSize = getMeasuredWidth() / 2;
 
         int delta = screenCenter - (getScrollForPage(page) + halfScreenSize);
         int count = getChildCount();
@@ -1145,8 +1089,8 @@
         } else {
             View child = getChildAt(index);
 
-            int scrollOffset = scrollOffset = mIsRtl ? getPaddingRight() : getPaddingLeft();
-            int baselineX = mPageScrolls[index] + scrollOffset + getViewportOffsetX();
+            int scrollOffset = mIsRtl ? getPaddingRight() : getPaddingLeft();
+            int baselineX = mPageScrolls[index] + scrollOffset;
             return (int) (child.getX() - baselineX);
         }
     }
@@ -1154,7 +1098,7 @@
     protected void dampedOverScroll(float amount) {
         if (Float.compare(amount, 0f) == 0) return;
 
-        int overScrollAmount = OverScroll.dampedScroll(amount, getViewportWidth());
+        int overScrollAmount = OverScroll.dampedScroll(amount, getMeasuredWidth());
         if (amount < 0) {
             mOverScrollX = overScrollAmount;
             super.scrollTo(mOverScrollX, getScrollY());
@@ -1328,8 +1272,8 @@
                                     // dragViewIndex < pageUnderPointIndex, so after we remove the
                                     // drag view all subsequent views to pageUnderPointIndex will
                                     // shift down.
-                                    int oldX = getViewportOffsetX() + getChildOffset(i);
-                                    int newX = getViewportOffsetX() + getChildOffset(i + shiftDelta);
+                                    int oldX = getChildOffset(i);
+                                    int newX = getChildOffset(i + shiftDelta);
 
                                     // Animate the view translation from its old position to its new
                                     // position
@@ -1600,20 +1544,20 @@
         }
     }
 
-    int getPageNearestToCenterOfScreen() {
+    public int getPageNearestToCenterOfScreen() {
         return getPageNearestToCenterOfScreen(getScrollX());
     }
 
     private int getPageNearestToCenterOfScreen(int scaledScrollX) {
-        int screenCenter = getViewportOffsetX() + scaledScrollX + (getViewportWidth() / 2);
+        int screenCenter = scaledScrollX + (getMeasuredWidth() / 2);
         int minDistanceFromScreenCenter = Integer.MAX_VALUE;
         int minDistanceFromScreenCenterIndex = -1;
         final int childCount = getChildCount();
         for (int i = 0; i < childCount; ++i) {
-            View layout = (View) getPageAt(i);
+            View layout = getPageAt(i);
             int childWidth = layout.getMeasuredWidth();
             int halfChildWidth = (childWidth / 2);
-            int childCenter = getViewportOffsetX() + getChildOffset(i) + halfChildWidth;
+            int childCenter = getChildOffset(i) + halfChildWidth;
             int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
             if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
                 minDistanceFromScreenCenter = distanceFromScreenCenter;
@@ -1650,7 +1594,7 @@
 
     protected void snapToPageWithVelocity(int whichPage, int velocity) {
         whichPage = validateNewPage(whichPage);
-        int halfScreenSize = getViewportWidth() / 2;
+        int halfScreenSize = getMeasuredWidth() / 2;
 
         final int newX = getScrollForPage(whichPage);
         int delta = newX - getUnboundedScrollX();
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index bdfeae1..a658d58 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -469,8 +469,11 @@
         }
         RectF boxRect = drawBoxWithShadow(c, size, size);
 
-        Bitmap icon = LauncherIcons.createScaledBitmapWithoutShadow(
-                mutateOnMainThread(info.getFullResIcon(mIconCache)), mContext, 0);
+        LauncherIcons li = LauncherIcons.obtain(mContext);
+        Bitmap icon = li.createScaledBitmapWithoutShadow(
+                mutateOnMainThread(info.getFullResIcon(mIconCache)), 0);
+        li.recycle();
+
         Rect src = new Rect(0, 0, icon.getWidth(), icon.getHeight());
 
         boxRect.set(0, 0, iconSize, iconSize);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index efafb9c..de3b09a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -207,7 +207,7 @@
     private boolean mStripScreensOnPageStopMoving = false;
 
     private DragPreviewProvider mOutlineProvider = null;
-    private final boolean mWorkspaceFadeInAdjacentScreens;
+    private boolean mWorkspaceFadeInAdjacentScreens;
 
     final WallpaperOffsetInterpolator mWallpaperOffset;
     private boolean mUnlockWallpaperFromDefaultPageOnLayout;
@@ -292,8 +292,6 @@
 
         mLauncher = Launcher.getLauncher(context);
         mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
         mWallpaperManager = WallpaperManager.getInstance(context);
 
         mWallpaperOffset = new WallpaperOffsetInterpolator(this);
@@ -310,6 +308,30 @@
     @Override
     public void setInsets(Rect insets) {
         mInsets.set(insets);
+
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
+        mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
+
+        Rect padding = grid.workspacePadding;
+        setPadding(padding.left, padding.top, padding.right, padding.bottom);
+
+        if (grid.shouldFadeAdjacentWorkspaceScreens()) {
+            // In landscape mode the page spacing is set to the default.
+            setPageSpacing(grid.defaultPageSpacingPx);
+        } else {
+            // In portrait, we want the pages spaced such that there is no
+            // overhang of the previous / next page into the current page viewport.
+            // We assume symmetrical padding in portrait mode.
+            setPageSpacing(Math.max(grid.defaultPageSpacingPx, padding.left + 1));
+        }
+
+        int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
+        int paddingBottom = grid.cellLayoutBottomPaddingPx;
+        for (int i = mWorkspaceScreens.size() - 1; i >= 0; i--) {
+            mWorkspaceScreens.valueAt(i)
+                    .setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
+        }
     }
 
     /**
@@ -431,12 +453,9 @@
      */
     protected void initWorkspace() {
         mCurrentPage = DEFAULT_PAGE;
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        setWillNotDraw(false);
         setClipToPadding(false);
 
         setupLayoutTransition();
-        mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
 
         // Set the wallpaper dimensions when Launcher starts up
         setWallpaperDimension();
@@ -1133,7 +1152,7 @@
                 mLauncherOverlay.onScrollInteractionBegin();
             }
 
-            mLastOverlayScroll = Math.abs(amount / getViewportWidth());
+            mLastOverlayScroll = Math.abs(amount / getMeasuredWidth());
             mLauncherOverlay.onScrollChange(mLastOverlayScroll, mIsRtl);
         } else {
             dampedOverScroll(amount);
@@ -1331,7 +1350,7 @@
 
     private void updatePageAlphaValues() {
         if (!workspaceInModalState() && !mIsSwitchingState) {
-            int screenCenter = getScrollX() + getViewportWidth() / 2;
+            int screenCenter = getScrollX() + getMeasuredWidth() / 2;
             for (int i = 0; i < getChildCount(); i++) {
                 CellLayout child = (CellLayout) getChildAt(i);
                 if (child != null) {
@@ -1415,8 +1434,8 @@
         if (mChildrenLayersEnabled) {
             final int screenCount = getChildCount();
 
-            float visibleLeft = getViewportOffsetX();
-            float visibleRight = visibleLeft + getViewportWidth();
+            float visibleLeft = 0;
+            float visibleRight = visibleLeft + getMeasuredWidth();
             float scaleX = getScaleX();
             if (scaleX < 1 && scaleX > 0) {
                 float mid = getMeasuredWidth() / 2;
@@ -2164,7 +2183,7 @@
         // Use the absolute left instead of the child left, as we want the visible area
         // irrespective of the visible child. Since the view can only scroll horizontally, the
         // top position is not affected.
-        mTempXY[0] = getViewportOffsetX() + getPaddingLeft() + boundingLayout.getLeft();
+        mTempXY[0] = getPaddingLeft() + boundingLayout.getLeft();
         mTempXY[1] = child.getTop() + boundingLayout.getTop();
 
         float scale = mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY);
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 9ed86ed..cf35e52 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -154,10 +154,13 @@
 
     private void applyChildState(LauncherState state, CellLayout cl, int childIndex,
             PageAlphaProvider pageAlphaProvider, PropertySetter propertySetter) {
+        float pageAlpha = pageAlphaProvider.getPageAlpha(childIndex);
+        int drawableAlpha = Math.round(pageAlpha * (state.hasScrim ? 255 : 0));
+
         propertySetter.setInt(cl.getScrimBackground(),
-                DRAWABLE_ALPHA, state.hasScrim ? 255 : 0, Interpolators.ZOOM_IN);
+                DRAWABLE_ALPHA, drawableAlpha, Interpolators.ZOOM_IN);
         propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
-                pageAlphaProvider.getPageAlpha(childIndex), pageAlphaProvider.interpolator);
+                pageAlpha, pageAlphaProvider.interpolator);
     }
 
     public static class PropertySetter {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 7444545..59bdf4b 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.Insettable;
+import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
@@ -59,7 +60,6 @@
 import com.android.launcher3.util.ComponentKeyMapper;
 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 java.util.HashMap;
@@ -70,10 +70,7 @@
  * The all apps view container.
  */
 public class AllAppsContainerView extends RelativeLayout implements DragSource,
-        View.OnLongClickListener, Insettable, DeviceProfile.LauncherLayoutChangeListener,
-        BubbleTextView.BubbleTextShadowHandler {
-
-    protected final Rect mBasePadding = new Rect();
+        View.OnLongClickListener, Insettable, BubbleTextView.BubbleTextShadowHandler {
 
     private final Launcher mLauncher;
     private final AdapterHolder[] mAH;
@@ -92,7 +89,6 @@
     private int mNumAppsPerRow;
     private int mNumPredictedAppsPerRow;
 
-    private TransformingTouchDelegate mTouchDelegate;
     private boolean mUsingTabs;
     private boolean mHasPredictions = false;
     private boolean mSearchModeWhileUsingTabs = false;
@@ -130,47 +126,11 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-
-        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
-        grid.addLauncherLayoutChangedListener(this);
-
         applyTouchDelegate();
     }
 
     private void applyTouchDelegate() {
-        RecyclerView rv = getActiveRecyclerView();
-        mTouchDelegate = new TransformingTouchDelegate(rv);
-        mTouchDelegate.setBounds(
-                rv.getLeft() - mBasePadding.left,
-                rv.getTop() - mBasePadding.top,
-                rv.getRight() + mBasePadding.right,
-                rv.getBottom() + mBasePadding.bottom);
-        setTouchDelegate(mTouchDelegate);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
-        grid.removeLauncherLayoutChangedListener(this);
-    }
-
-    /**
-     * Calculate the background padding as it can change due to insets/content padding change.
-     */
-    @Override
-    public void onLauncherLayoutChanged() {
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        if (!grid.isVerticalBarLayout()) {
-            return;
-        }
-
-        int[] padding = grid.getContainerPadding();
-        int paddingLeft = padding[0];
-        int paddingRight = padding[1];
-        mBasePadding.set(paddingLeft, 0, paddingRight, 0);
-        setPadding(paddingLeft, 0, paddingRight, 0);
+        // TODO: Reimplement once fast scroller is fixed.
     }
 
     @Override
@@ -294,12 +254,9 @@
 
         // This is a focus listener that proxies focus from a view into the list view.  This is to
         // work around the search box from getting first focus and showing the cursor.
-        setOnFocusChangeListener(new View.OnFocusChangeListener() {
-            @Override
-            public void onFocusChange(View v, boolean hasFocus) {
-                if (hasFocus && getActiveRecyclerView() != null) {
-                    getActiveRecyclerView().requestFocus();
-                }
+        setOnFocusChangeListener((v, hasFocus) -> {
+            if (hasFocus && getActiveRecyclerView() != null) {
+                getActiveRecyclerView().requestFocus();
             }
         });
 
@@ -309,8 +266,6 @@
         mSearchContainer = findViewById(R.id.search_container_all_apps);
         mSearchUiManager = (SearchUiManager) mSearchContainer;
         mSearchUiManager.initialize(this);
-
-        onLauncherLayoutChanged();
     }
 
     public SearchUiManager getSearchUiManager() {
@@ -320,8 +275,6 @@
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        // Update the number of items in the grid before we measure the view
-        grid.updateAppsViewNumCols();
 
         if (mNumAppsPerRow != grid.inv.numColumns ||
                 mNumPredictedAppsPerRow != grid.inv.numColumns) {
@@ -382,8 +335,12 @@
     @Override
     public void setInsets(Rect insets) {
         DeviceProfile grid = mLauncher.getDeviceProfile();
+        int leftRightPadding = grid.desiredWorkspaceLeftRightMarginPx
+                + grid.cellLayoutPaddingLeftRightPx;
+
         for (int i = 0; i < mAH.length; i++) {
             mAH[i].padding.bottom = insets.bottom;
+            mAH[i].padding.left = mAH[i].padding.right = leftRightPadding;
             mAH[i].applyPadding();
         }
         if (grid.isVerticalBarLayout()) {
@@ -392,29 +349,43 @@
             mlp.topMargin = insets.top;
             mlp.rightMargin = insets.right;
             setLayoutParams(mlp);
+            setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
         } else {
             View navBarBg = findViewById(R.id.nav_bar_bg);
             ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams();
             navBarBgLp.height = insets.bottom;
             navBarBg.setLayoutParams(navBarBgLp);
         }
+        InsettableFrameLayout.dispatchInsets(this, insets);
     }
 
     public void updateIconBadges(Set<PackageUserKey> updatedBadges) {
         final PackageUserKey packageUserKey = new PackageUserKey(null, null);
         for (int j = 0; j < mAH.length; j++) {
-            if (mAH[j].recyclerView != null) {
-                final int n = mAH[j].recyclerView.getChildCount();
-                for (int i = 0; i < n; i++) {
-                    View child = mAH[j].recyclerView.getChildAt(i);
-                    if (!(child instanceof BubbleTextView) || !(child.getTag() instanceof ItemInfo)) {
-                        continue;
-                    }
-                    ItemInfo info = (ItemInfo) child.getTag();
-                    if (packageUserKey.updateFromItemInfo(info) && updatedBadges.contains(packageUserKey)) {
-                        ((BubbleTextView) child).applyBadgeState(info, true /* animate */);
-                    }
-                }
+            updateIconBadges(updatedBadges, packageUserKey, mAH[j].recyclerView);
+        }
+        if (mHeader != null) {
+            updateIconBadges(updatedBadges, packageUserKey, mHeader.getPredictionRow());
+        }
+    }
+
+    private void updateIconBadges(Set<PackageUserKey> updatedBadges, PackageUserKey packageUserKey,
+            ViewGroup parent) {
+        if (parent == null) {
+            return;
+        }
+        final int n = parent.getChildCount();
+        for (int i = 0; i < n; i++) {
+            View child = parent.getChildAt(i);
+            if (child instanceof PredictionRowView) {
+                updateIconBadges(updatedBadges, packageUserKey, (PredictionRowView) child);
+            }
+            if (!(child instanceof BubbleTextView) || !(child.getTag() instanceof ItemInfo)) {
+                continue;
+            }
+            ItemInfo info = (ItemInfo) child.getTag();
+            if (packageUserKey.updateFromItemInfo(info) && updatedBadges.contains(packageUserKey)) {
+                ((BubbleTextView) child).applyBadgeState(info, true /* animate */);
             }
         }
     }
@@ -593,14 +564,6 @@
         }
     }
 
-    public void setRecyclerViewSidePadding(int left, int right) {
-        for (int i = 0; i < mAH.length; i++) {
-            mAH[i].padding.left = left;
-            mAH[i].padding.right = right;
-            mAH[i].applyPadding();
-        }
-    }
-
     public void setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled) {
         for (int i = 0; i < mAH.length; i++) {
             mAH[i].applyVerticalFadingEdgeEnabled(enabled);
@@ -625,6 +588,12 @@
         return mHeader != null && mHeader.getVisibility() == View.VISIBLE;
     }
 
+    public void onScrollUpEnd() {
+        if (mUsingTabs) {
+            ((PersonalWorkSlidingTabStrip) findViewById(R.id.tabs)).peekWorkTabIfNecessary();
+        }
+    }
+
     public class AdapterHolder {
         public static final int MAIN = 0;
         public static final int WORK = 1;
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 234eb81..769f9ba 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
 import com.android.launcher3.anim.SpringAnimationHandler;
+import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.discovery.AppDiscoveryAppInfo;
 import com.android.launcher3.discovery.AppDiscoveryItemView;
@@ -377,6 +378,11 @@
             case VIEW_TYPE_WORK_TAB_FOOTER:
                 WorkModeSwitch workModeToggle = holder.itemView.findViewById(R.id.work_mode_toggle);
                 workModeToggle.refresh();
+                TextView managedByLabel = holder.itemView.findViewById(R.id.managed_by_label);
+                boolean anyProfileQuietModeEnabled = UserManagerCompat.getInstance(
+                        managedByLabel.getContext()).isAnyProfileQuietModeEnabled();
+                managedByLabel.setText(anyProfileQuietModeEnabled
+                        ? R.string.work_mode_off_label : R.string.work_mode_on_label);
                 break;
         }
         if (mBindViewCallback != null) {
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 5830f74..d8a0d64 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -21,7 +21,6 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.graphics.GradientView;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.AllAppsScrim;
@@ -63,6 +62,7 @@
 
     private final Launcher mLauncher;
     private final boolean mIsDarkTheme;
+    private final boolean mIsVerticalLayout;
 
     // Animation in this class is controlled by a single variable {@link mProgress}.
     // Visually, it represents top y coordinate of the all apps container if multiplied with
@@ -83,6 +83,7 @@
         mProgress = 1f;
 
         mIsDarkTheme = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
+        mIsVerticalLayout = mLauncher.getDeviceProfile().isVerticalBarLayout();
     }
 
     public float getShiftRange() {
@@ -95,18 +96,6 @@
         mAppsView.setVisibility(View.VISIBLE);
     }
 
-    private void updateLightStatusBar(float shift) {
-        // Use a light system UI (dark icons) if all apps is behind at least half of the status bar.
-        boolean forceChange = shift <= mShiftRange / 4;
-        if (forceChange) {
-            mLauncher.getSystemUiController().updateUiState(
-                    SystemUiController.UI_STATE_ALL_APPS, !mIsDarkTheme);
-        } else {
-            mLauncher.getSystemUiController().updateUiState(
-                    SystemUiController.UI_STATE_ALL_APPS, 0);
-        }
-    }
-
     /**
      * Note this method should not be called outside this class. This is public because it is used
      * in xml-based animations which also handle updating the appropriate UI.
@@ -124,24 +113,30 @@
         float alpha = 1 - workspaceHotseatAlpha;
         float hotseatAlpha = mHotseatAccelInterpolator.getInterpolation(workspaceHotseatAlpha);
 
-        mAppsView.setAlpha(alpha);
         mAppsView.setTranslationY(shiftCurrent);
-
         if (mAllAppsScrim == null) {
             mAllAppsScrim = mLauncher.findViewById(R.id.all_apps_scrim);
         }
         float hotseatTranslation = -mShiftRange + shiftCurrent;
         mAllAppsScrim.setProgress(hotseatTranslation, alpha);
 
-        if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+        if (!mIsVerticalLayout) {
+            mAppsView.setAlpha(alpha);
             mWorkspace.setHotseatTranslationAndAlpha(Workspace.Direction.Y, hotseatTranslation,
                     hotseatAlpha);
-        } else {
-            mWorkspace.setHotseatTranslationAndAlpha(Workspace.Direction.Y,
-                    PARALLAX_COEFFICIENT * hotseatTranslation, hotseatAlpha);
-        }
 
-        updateLightStatusBar(shiftCurrent);
+            // Use a light system UI (dark icons) if all apps is behind at least half of the
+            // status bar.
+            boolean forceChange = shiftCurrent <= mShiftRange / 4;
+            if (forceChange) {
+                mLauncher.getSystemUiController().updateUiState(
+                        SystemUiController.UI_STATE_ALL_APPS, !mIsDarkTheme);
+            } else {
+                mLauncher.getSystemUiController().updateUiState(
+                        SystemUiController.UI_STATE_ALL_APPS, 0);
+            }
+
+        }
     }
 
     public float getProgress() {
@@ -154,7 +149,7 @@
      */
     @Override
     public void setState(LauncherState state) {
-        setProgress(state.verticalProgress);
+        setProgress(state.getVerticalProgress(mLauncher));
         onProgressAnimationEnd();
     }
 
@@ -165,15 +160,15 @@
     @Override
     public void setStateWithAnimation(LauncherState toState,
             AnimatorSetBuilder builder, AnimationConfig config) {
-        if (Float.compare(mProgress, toState.verticalProgress) == 0) {
+        float targetProgress = toState.getVerticalProgress(mLauncher);
+        if (Float.compare(mProgress, targetProgress) == 0) {
             // Fail fast
             onProgressAnimationEnd();
             return;
         }
 
         Interpolator interpolator = config.userControlled ? LINEAR : FAST_OUT_SLOW_IN;
-        ObjectAnimator anim = ObjectAnimator.ofFloat(
-                this, PROGRESS, mProgress, toState.verticalProgress);
+        ObjectAnimator anim = ObjectAnimator.ofFloat(this, PROGRESS, mProgress, targetProgress);
         anim.setDuration(config.duration);
         anim.setInterpolator(interpolator);
         anim.addListener(getProgressAnimatorListener());
@@ -221,6 +216,10 @@
         } else if (Float.compare(mProgress, 0f) == 0) {
             mHotseat.setVisibility(View.INVISIBLE);
             mAppsView.setVisibility(View.VISIBLE);
+            mAppsView.onScrollUpEnd();
+        } else {
+            mAppsView.setVisibility(View.VISIBLE);
+            mHotseat.setVisibility(View.VISIBLE);
         }
     }
 }
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 1419a2a..e4f8ad3 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -176,6 +176,7 @@
         mAnimator.start();
         mHeaderCollapsed = false;
         mSnappedScrolledY = -mMaxTranslation;
+        mCurrentRV.scrollToTop();
     }
 
     public boolean isExpanded() {
diff --git a/src/com/android/launcher3/allapps/LandscapeFastScroller.java b/src/com/android/launcher3/allapps/LandscapeFastScroller.java
deleted file mode 100644
index cdde657..0000000
--- a/src/com/android/launcher3/allapps/LandscapeFastScroller.java
+++ /dev/null
@@ -1,63 +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.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-
-import com.android.launcher3.views.RecyclerViewFastScroller;
-
-/**
- * Extension of {@link RecyclerViewFastScroller} to be used in landscape layout.
- */
-public class LandscapeFastScroller extends RecyclerViewFastScroller {
-
-    public LandscapeFastScroller(Context context) {
-        super(context);
-    }
-
-    public LandscapeFastScroller(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public LandscapeFastScroller(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    public boolean handleTouchEvent(MotionEvent ev) {
-        // We handle our own touch event, no need to handle recycler view touch delegates.
-        return false;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        event.offsetLocation(0, -mRv.getPaddingTop());
-        if (super.handleTouchEvent(event)) {
-            getParent().requestDisallowInterceptTouchEvent(true);
-        }
-        event.offsetLocation(0, mRv.getPaddingTop());
-        return true;
-    }
-
-    @Override
-    public boolean shouldBlockIntercept(int x, int y) {
-        // If the user touched the scroll bar area, block swipe
-        return x >= 0 && x < getWidth() && y >= 0 && y < getHeight();
-    }
-}
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index 47dc36d..393884e 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -15,7 +15,9 @@
  */
 package com.android.launcher3.allapps;
 
+import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.support.annotation.NonNull;
@@ -25,17 +27,27 @@
 import android.widget.Button;
 import android.widget.LinearLayout;
 
+import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.util.Themes;
 
 /**
  * Supports two indicator colors, dedicated for personal and work tabs.
  */
 public class PersonalWorkSlidingTabStrip extends LinearLayout {
+    private static final int POSITION_PERSONAL = 0;
+    private static final int POSITION_WORK = 1;
+    private static final int PEEK_DURATION = 1000;
+    private static final float PEAK_OFFSET = 0.4f;
+
+    private static final String KEY_SHOWED_PEEK_WORK_TAB = "showed_peek_work_tab";
+
     private final Paint mPersonalTabIndicatorPaint;
     private final Paint mWorkTabIndicatorPaint;
     private final Paint mDividerPaint;
+    private final SharedPreferences mSharedPreferences;
 
     private int mSelectedIndicatorHeight;
     private int mIndicatorLeft = -1;
@@ -64,7 +76,10 @@
 
         mDividerPaint = new Paint();
         mDividerPaint.setColor(Themes.getAttrColor(context, android.R.attr.colorControlHighlight));
-        mDividerPaint.setStrokeWidth(getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
+        mDividerPaint.setStrokeWidth(
+                getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
+
+        mSharedPreferences = Launcher.getLauncher(getContext()).getSharedPrefs();
     }
 
     public void updateIndicatorPosition(int position, float positionOffset) {
@@ -141,4 +156,26 @@
         boolean isPersonal = mIsRtl ^ firstHalf;
         return isPersonal ? mPersonalTabIndicatorPaint : mWorkTabIndicatorPaint;
     }
+
+    public void peekWorkTabIfNecessary() {
+        if (mSharedPreferences.getBoolean(KEY_SHOWED_PEEK_WORK_TAB, false)) {
+            return;
+        }
+        if (mIndicatorPosition != POSITION_PERSONAL) {
+            return;
+        }
+        peekWorkTab();
+        mSharedPreferences.edit().putBoolean(KEY_SHOWED_PEEK_WORK_TAB, true).apply();
+    }
+
+    private void peekWorkTab() {
+        final boolean isRtl = Utilities.isRtl(getResources());
+        ValueAnimator animator = ValueAnimator.ofFloat(0, isRtl ? 1 - PEAK_OFFSET : PEAK_OFFSET, 0);
+        animator.setDuration(PEEK_DURATION);
+        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        animator.addUpdateListener(
+                animation -> updateIndicatorPosition(mIndicatorPosition,
+                        (float) animation.getAnimatedValue()));
+        animator.start();
+    }
 }
diff --git a/src/com/android/launcher3/allapps/PredictionRowView.java b/src/com/android/launcher3/allapps/PredictionRowView.java
index 4aacc6d..c19d663 100644
--- a/src/com/android/launcher3/allapps/PredictionRowView.java
+++ b/src/com/android/launcher3/allapps/PredictionRowView.java
@@ -28,9 +28,12 @@
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ComponentKeyMapper;
 import com.android.launcher3.util.Themes;
@@ -40,7 +43,8 @@
 import java.util.HashMap;
 import java.util.List;
 
-public class PredictionRowView extends LinearLayout {
+public class PredictionRowView extends LinearLayout implements
+        UserEventDispatcher.LogContainerProvider {
 
     private static final String TAG = "PredictionRowView";
 
@@ -49,7 +53,7 @@
     // The set of predicted app component names
     private final List<ComponentKeyMapper<AppInfo>> mPredictedAppComponents = new ArrayList<>();
     // The set of predicted apps resolved from the component names and the current set of apps
-    private final List<AppInfo> mPredictedApps = new ArrayList<>();
+    private final ArrayList<AppInfo> mPredictedApps = new ArrayList<>();
     private final Paint mPaint;
     // This adapter is only used to create an identical item w/ same behavior as in the all apps RV
     private AllAppsGridAdapter mAdapter;
@@ -134,6 +138,7 @@
     }
 
     private void onAppsUpdated() {
+        int childCountBefore = getChildCount();
         if (getChildCount() != mNumPredictedAppsPerRow) {
             while (getChildCount() > mNumPredictedAppsPerRow) {
                 removeViewAt(0);
@@ -160,6 +165,13 @@
                 icon.setVisibility(View.INVISIBLE);
             }
         }
+
+        if (getChildCount() > 0 && childCountBefore == 0
+                || getChildCount() == 0 && childCountBefore > 0) {
+            // setting up header to adjust the height
+            // only necessary if childcount switches from/to 0
+            Launcher.getLauncher(getContext()).getAppsView().setupHeader();
+        }
     }
 
     private List<AppInfo> processPredictedAppComponents(
@@ -199,4 +211,17 @@
             canvas.drawLine(x1, y, x2, y, mPaint);
         }
     }
+
+    @Override
+    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+            LauncherLogProto.Target targetParent) {
+        for (int i = 0; i < mPredictedApps.size(); i++) {
+            AppInfo appInfo = mPredictedApps.get(i);
+            if (appInfo == info) {
+                targetParent.containerType = LauncherLogProto.ContainerType.PREDICTION;
+                target.predictedRank = i;
+                break;
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index 32c9ce3..e7cf092 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -55,22 +55,9 @@
     }
 
     public void refresh() {
-        setCheckedInternal(!isAnyProfileQuietModeEnabled());
-        setEnabled(true);
-    }
-
-    private boolean isAnyProfileQuietModeEnabled() {
         UserManagerCompat userManager = UserManagerCompat.getInstance(getContext());
-        List<UserHandle> userProfiles = userManager.getUserProfiles();
-        for (UserHandle userProfile : userProfiles) {
-            if (Process.myUserHandle().equals(userProfile)) {
-                continue;
-            }
-            if (userManager.isQuietModeEnabled(userProfile)) {
-                return true;
-            }
-        }
-        return false;
+        setCheckedInternal(!userManager.isAnyProfileQuietModeEnabled());
+        setEnabled(true);
     }
 
     private void trySetQuietModeEnabledToAllProfilesAsync(boolean enabled) {
@@ -91,7 +78,7 @@
                     if (Process.myUserHandle().equals(userProfile)) {
                         continue;
                     }
-                    showConfirm |= !userManager.trySetQuietModeEnabled(enabled, userProfile);
+                    showConfirm |= !userManager.requestQuietModeEnabled(enabled, userProfile);
                 }
                 return showConfirm;
             }
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 6819386..68e9847 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -33,6 +33,12 @@
  */
 public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
 
+    /**
+     * Creates an animation controller for the provided animation.
+     * The actual duration does not matter as the animation is manually controlled. It just
+     * needs to be larger than the total number of pixels so that we don't have jittering due
+     * to float (animation-fraction * total duration) to int conversion.
+     */
     public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
 
         /**
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
index 02ec9f0..72d49f0 100644
--- a/src/com/android/launcher3/badge/BadgeRenderer.java
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -38,27 +38,32 @@
     private static final String TAG = "BadgeRenderer";
 
     // The badge sizes are defined as percentages of the app icon size.
-    private static final float SIZE_PERCENTAGE = 0.23f;
+    private static final float SIZE_PERCENTAGE = 0.38f;
+
+    // Extra scale down of the dot
+    private static final float DOT_SCALE = 0.6f;
+
     // Used to expand the width of the badge for each additional digit.
     private static final float OFFSET_PERCENTAGE = 0.02f;
 
-    private final int mSize;
+    private final float mDotCenterOffset;
     private final int mOffset;
     private final float mCircleRadius;
     private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
 
     private final Bitmap mBackgroundWithShadow;
-    private final int mBitmapOffset;
+    private final float mBitmapOffset;
 
     public BadgeRenderer(int iconSizePx) {
-        mSize = (int) (SIZE_PERCENTAGE * iconSizePx);
+        mDotCenterOffset = SIZE_PERCENTAGE * iconSizePx;
         mOffset = (int) (OFFSET_PERCENTAGE * iconSizePx);
 
+        int size = (int) (DOT_SCALE * mDotCenterOffset);
         ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
-        mBackgroundWithShadow = builder.setupBlurForSize(mSize).createPill(mSize, mSize);
+        mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
         mCircleRadius = builder.radius;
 
-        mBitmapOffset = -mBackgroundWithShadow.getHeight() / 2; // Same as width.
+        mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width.
     }
 
     /**
@@ -77,8 +82,8 @@
         }
         canvas.save(Canvas.MATRIX_SAVE_FLAG);
         // We draw the badge relative to its center.
-        int badgeCenterX = iconBounds.right - mSize / 2;
-        int badgeCenterY = iconBounds.top + mSize / 2;
+        float badgeCenterX = iconBounds.right - mDotCenterOffset / 2;
+        float badgeCenterY = iconBounds.top + mDotCenterOffset / 2;
 
         int offsetX = Math.min(mOffset, spaceForOffset.x);
         int offsetY = Math.min(mOffset, spaceForOffset.y);
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
index 5cd90b1..173d0d1 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
@@ -137,7 +137,9 @@
             ShortcutInfoCompat compat = new ShortcutInfoCompat(request.getShortcutInfo());
             ShortcutInfo info = new ShortcutInfo(compat, context);
             // Apply the unbadged icon and fetch the actual icon asynchronously.
-            LauncherIcons.createShortcutIcon(compat, context, false /* badged */).applyTo(info);
+            LauncherIcons li = LauncherIcons.obtain(context);
+            li.createShortcutIcon(compat, false /* badged */).applyTo(info);
+            li.recycle();
             LauncherAppState.getInstance(context).getModel()
                     .updateAndBindShortcutInfo(info, compat);
             return info;
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index 197f798..62055dc 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -63,5 +63,6 @@
     public abstract boolean isUserUnlocked(UserHandle user);
 
     public abstract boolean isDemoUser();
-    public abstract boolean trySetQuietModeEnabled(boolean enableQuietMode, UserHandle user);
+    public abstract boolean requestQuietModeEnabled(boolean enableQuietMode, UserHandle user);
+    public abstract boolean isAnyProfileQuietModeEnabled();
 }
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index e6cc319..e57786d 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -83,7 +83,12 @@
     }
 
     @Override
-    public boolean trySetQuietModeEnabled(boolean enableQuietMode, UserHandle user) {
+    public boolean requestQuietModeEnabled(boolean enableQuietMode, UserHandle user) {
+        return false;
+    }
+
+    @Override
+    public boolean isAnyProfileQuietModeEnabled() {
         return false;
     }
 
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVN.java b/src/com/android/launcher3/compat/UserManagerCompatVN.java
index 50a0217..3733565 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVN.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVN.java
@@ -19,8 +19,11 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Build;
+import android.os.Process;
 import android.os.UserHandle;
 
+import java.util.List;
+
 @TargetApi(Build.VERSION_CODES.N)
 public class UserManagerCompatVN extends UserManagerCompatVM {
 
@@ -37,5 +40,19 @@
     public boolean isUserUnlocked(UserHandle user) {
         return mUserManager.isUserUnlocked(user);
     }
+
+    @Override
+    public boolean isAnyProfileQuietModeEnabled() {
+        List<UserHandle> userProfiles = getUserProfiles();
+        for (UserHandle userProfile : userProfiles) {
+            if (Process.myUserHandle().equals(userProfile)) {
+                continue;
+            }
+            if (isQuietModeEnabled(userProfile)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
 
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVP.java b/src/com/android/launcher3/compat/UserManagerCompatVP.java
index a0bf0ab..2e8a8eb 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVP.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVP.java
@@ -26,29 +26,29 @@
 public class UserManagerCompatVP extends UserManagerCompatVNMr1 {
     private static final String TAG = "UserManagerCompatVP";
 
-    private Method mTrySetQuietModeEnabledMethod;
+    private Method mRequestQuietModeEnabled;
 
     UserManagerCompatVP(Context context) {
         super(context);
         // TODO: Replace it with proper API call once SDK is ready.
         try {
-            mTrySetQuietModeEnabledMethod = UserManager.class.getDeclaredMethod(
-                    "trySetQuietModeEnabled", boolean.class, UserHandle.class);
+            mRequestQuietModeEnabled = UserManager.class.getDeclaredMethod(
+                    "requestQuietModeEnabled", boolean.class, UserHandle.class);
         } catch (NoSuchMethodException e) {
-            Log.e(TAG, "trySetQuietModeEnabled is not available", e);
+            Log.e(TAG, "requestQuietModeEnabled is not available", e);
         }
     }
 
     @Override
-    public boolean trySetQuietModeEnabled(boolean enableQuietMode, UserHandle user) {
-        if (mTrySetQuietModeEnabledMethod == null) {
+    public boolean requestQuietModeEnabled(boolean enableQuietMode, UserHandle user) {
+        if (mRequestQuietModeEnabled == null) {
             return false;
         }
         try {
             return (boolean)
-                    mTrySetQuietModeEnabledMethod.invoke(mUserManager, enableQuietMode, user);
+                    mRequestQuietModeEnabled.invoke(mUserManager, enableQuietMode, user);
         } catch (IllegalAccessException | InvocationTargetException e) {
-            Log.e(TAG, "Failed to invoke mTrySetQuietModeEnabledMethod", e);
+            Log.e(TAG, "Failed to invoke mRequestQuietModeEnabled", e);
         }
         return false;
     }
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 0d92d45..2d0e630 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -31,8 +31,6 @@
     public static final boolean IS_DOGFOOD_BUILD = false;
     public static final String AUTHORITY = "com.android.launcher3.settings".intern();
 
-    // Custom flags go below this
-    public static boolean LAUNCHER3_DISABLE_ICON_NORMALIZATION = false;
     // When enabled allows to use any point on the fast scrollbar to start dragging.
     public static final boolean LAUNCHER3_DIRECT_SCROLL = true;
     // When enabled the promise icon is visible in all apps while installation an app.
@@ -46,10 +44,6 @@
     public static final boolean QSB_ON_FIRST_SCREEN = true;
     // When enabled the all-apps icon is not added to the hotseat.
     public static final boolean NO_ALL_APPS_ICON = true;
-    // When enabled, icons not supporting {@link AdaptiveIconDrawable} will be wrapped in {@link FixedScaleDrawable}.
-    public static final boolean LEGACY_ICON_TREATMENT = true;
-    // When enabled, adaptive icons would have shadows baked when being stored to icon cache.
-    public static final boolean ADAPTIVE_ICON_SHADOW = true;
     // When enabled, app discovery will be enabled if service is implemented
     public static final boolean DISCOVERY_ENABLED = false;
 
@@ -63,4 +57,6 @@
     public static final boolean ALL_APPS_TABS_ENABLED = true;
     // When enabled prediction row is rendered as it's own custom view
     public static final boolean ALL_APPS_PREDICTION_ROW_VIEW = true;
+
+    public static final boolean ENABLE_TWO_SWIPE_TARGETS = true;
 }
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 11ff88f..a59b899 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -224,8 +224,10 @@
                     mBadge = getBadge(info, appState, outObj[0]);
                     mBadge.setBounds(badgeBounds);
 
+                    LauncherIcons li = LauncherIcons.obtain(mLauncher);
                     Utilities.scaleRectAboutCenter(bounds,
-                            IconNormalizer.getInstance(mLauncher).getScale(dr, null, null, null));
+                            li.getNormalizer().getScale(dr, null, null, null));
+                    li.recycle();
                     AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) dr;
 
                     // Shrink very tiny bit so that the clip path is smaller than the original bitmap
@@ -367,9 +369,9 @@
                 return new FixedSizeEmptyDrawable(iconSize);
             }
             ShortcutInfoCompat si = (ShortcutInfoCompat) obj;
-            Bitmap badge = LauncherIcons.getShortcutInfoBadge(si, appState.getIconCache())
-                    .iconBitmap;
-
+            LauncherIcons li = LauncherIcons.obtain(appState.getContext());
+            Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).iconBitmap;
+            li.recycle();
             float badgeSize = mLauncher.getResources().getDimension(R.dimen.profile_badge_size);
             float insetFraction = (iconSize - badgeSize) / iconSize;
             return new InsetDrawable(new FastBitmapDrawable(badge),
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index d4c396a..8abafb0 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 
 import android.animation.Animator;
@@ -453,9 +454,8 @@
      */
     @SuppressLint("InflateParams")
     static Folder fromXml(Launcher launcher) {
-        return (Folder) launcher.getLayoutInflater().inflate(
-                FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION
-                        ? R.layout.user_folder : R.layout.user_folder_icon_normalized, null);
+        return (Folder) launcher.getLayoutInflater()
+                .inflate(R.layout.user_folder_icon_normalized, null);
     }
 
     private void startAnimation(final AnimatorSet a) {
@@ -930,7 +930,12 @@
         int centeredTop = centerY - height / 2;
 
         // We need to bound the folder to the currently visible workspace area
-        mLauncher.getWorkspace().getPageAreaRelativeToDragLayer(sTempRect);
+        if (mLauncher.isInState(OVERVIEW)) {
+            mLauncher.getDragLayer().getDescendantRectRelativeToSelf(mLauncher.getOverviewPanel(),
+                    sTempRect);
+        } else {
+            mLauncher.getWorkspace().getPageAreaRelativeToDragLayer(sTempRect);
+        }
         int left = Math.min(Math.max(sTempRect.left, centeredLeft),
                 sTempRect.right- width);
         int top = Math.min(Math.max(sTempRect.top, centeredTop),
diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java
index 5ee6a30..bd20c87 100644
--- a/src/com/android/launcher3/graphics/IconNormalizer.java
+++ b/src/com/android/launcher3/graphics/IconNormalizer.java
@@ -37,10 +37,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 
-import java.io.File;
-import java.io.FileOutputStream;
 import java.nio.ByteBuffer;
-import java.util.Random;
 
 public class IconNormalizer {
 
@@ -64,9 +61,6 @@
     private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
     private static final float SCALE_NOT_INITIALIZED = 0;
 
-    private static final Object LOCK = new Object();
-    private static IconNormalizer sIconNormalizer;
-
     private final int mMaxSize;
     private final Bitmap mBitmap;
     private final Bitmap mBitmapARGB;
@@ -88,11 +82,8 @@
     private final Paint mPaintIcon;
     private final Canvas mCanvasARGB;
 
-    private final File mDir;
-    private int mFileId;
-    private final Random mRandom;
-
-    private IconNormalizer(Context context) {
+    /** package private **/
+    IconNormalizer(Context context) {
         // Use twice the icon size as maximum size to avoid scaling down twice.
         mMaxSize = LauncherAppState.getIDP(context).iconBitmapSize * 2;
         mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
@@ -124,9 +115,6 @@
 
         mMatrix = new Matrix();
         mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
-
-        mDir = context.getExternalFilesDir(null);
-        mRandom = new Random();
     }
 
     /**
@@ -148,18 +136,9 @@
         // Condition 2:
         // Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation
         // should generate transparent image, if the actual icon is equivalent to the shape.
-        mFileId = mRandom.nextInt();
         mBitmapARGB.eraseColor(Color.TRANSPARENT);
         mCanvasARGB.drawBitmap(mBitmap, 0, 0, mPaintIcon);
 
-        if (DEBUG) {
-            final File beforeFile = new File(mDir, "isShape" + mFileId + "_before.png");
-            try {
-                mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100,
-                        new FileOutputStream(beforeFile));
-            } catch (Exception e) {}
-        }
-
         // Fit the shape within the icon's bounding box
         mMatrix.reset();
         mMatrix.setScale(mBounds.width(), mBounds.height());
@@ -172,24 +151,8 @@
         // DST_OUT operation around the mask path outline
         mCanvasARGB.drawPath(maskPath, mPaintMaskShapeOutline);
 
-        boolean isTrans = isTransparentBitmap(mBitmapARGB);
-        if (DEBUG) {
-            final File afterFile = new File(mDir,
-                    "isShape" + mFileId + "_after_" + isTrans + ".png");
-            try {
-                mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100,
-                        new FileOutputStream(afterFile));
-            } catch (Exception e) {}
-        }
-
         // Check if the result is almost transparent
-        if (!isTrans) {
-            if (DEBUG) {
-                Log.d(TAG, "Not same as mask shape");
-            }
-            return false;
-        }
-        return true;
+        return isTransparentBitmap(mBitmapARGB);
     }
 
     /**
@@ -203,19 +166,13 @@
                 mBounds.left, mBounds.top,
                 w, h);
         int sum = 0;
-        for (int i = 0; i < w * h; i++) {
+        for (int i = w * h - 1; i >= 0; i--) {
             if(Color.alpha(mPixelsARGB[i]) > MIN_VISIBLE_ALPHA) {
                     sum++;
             }
         }
         float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());
-        boolean transparentImage = percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
-        if (DEBUG) {
-            Log.d(TAG,
-                    "Total # pixel that is different (id=" + mFileId + "):" + percentageDiffPixels
-                            + "=" + sum + "/" + mBounds.width() * mBounds.height());
-        }
-        return transparentImage;
+        return percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
     }
 
     /**
@@ -416,13 +373,4 @@
             last = i;
         }
     }
-
-    public static IconNormalizer getInstance(Context context) {
-        synchronized (LOCK) {
-            if (sIconNormalizer == null) {
-                sIconNormalizer = new IconNormalizer(context);
-            }
-        }
-        return sIconNormalizer;
-    }
 }
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index fdb6313..34fc921 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -16,7 +16,11 @@
 
 package com.android.launcher3.graphics;
 
-import android.annotation.TargetApi;
+import static android.graphics.Paint.DITHER_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+
+import static com.android.launcher3.graphics.ShadowGenerator.BLUR_FACTOR;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -25,7 +29,6 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Paint;
 import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -41,11 +44,11 @@
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
@@ -56,32 +59,95 @@
 /**
  * Helper methods for generating various launcher icons
  */
-public class LauncherIcons {
+public class LauncherIcons implements AutoCloseable {
 
-    private static final Rect sOldBounds = new Rect();
-    private static final Canvas sCanvas = new Canvas();
+    public static final Object sPoolSync = new Object();
+    private static LauncherIcons sPool;
 
-    static {
-        sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
-                Paint.FILTER_BITMAP_FLAG));
+    /**
+     * Return a new Message instance from the global pool. Allows us to
+     * avoid allocating new objects in many cases.
+     */
+    public static LauncherIcons obtain(Context context) {
+        synchronized (sPoolSync) {
+            if (sPool != null) {
+                LauncherIcons m = sPool;
+                sPool = m.next;
+                m.next = null;
+                return m;
+            }
+        }
+        return new LauncherIcons(context);
+    }
+
+    /**
+     * Recycles a LauncherIcons that may be in-use.
+     */
+    public void recycle() {
+        synchronized (sPoolSync) {
+            next = sPool;
+            sPool = this;
+        }
+    }
+
+    @Override
+    public void close() {
+        recycle();
+    }
+
+    private final Rect mOldBounds = new Rect();
+    private final Context mContext;
+    private final Canvas mCanvas;
+    private final PackageManager mPm;
+
+    private final int mFillResIconDpi;
+    private final int mIconBitmapSize;
+
+    private IconNormalizer mNormalizer;
+    private ShadowGenerator mShadowGenerator;
+
+    // sometimes we store linked lists of these things
+    private LauncherIcons next;
+
+    private LauncherIcons(Context context) {
+        mContext = context.getApplicationContext();
+        mPm = mContext.getPackageManager();
+
+        InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
+        mFillResIconDpi = idp.fillResIconDpi;
+        mIconBitmapSize = idp.iconBitmapSize;
+
+        mCanvas = new Canvas();
+        mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
+    }
+
+    public ShadowGenerator getShadowGenerator() {
+        if (mShadowGenerator == null) {
+            mShadowGenerator = new ShadowGenerator(mContext);
+        }
+        return mShadowGenerator;
+    }
+
+    public IconNormalizer getNormalizer() {
+        if (mNormalizer == null) {
+            mNormalizer = new IconNormalizer(mContext);
+        }
+        return mNormalizer;
     }
 
     /**
      * Returns a bitmap suitable for the all apps view. If the package or the resource do not
      * exist, it returns null.
      */
-    public static BitmapInfo createIconBitmap(ShortcutIconResource iconRes, Context context) {
-        PackageManager packageManager = context.getPackageManager();
-        // the resource
+    public BitmapInfo createIconBitmap(ShortcutIconResource iconRes) {
         try {
-            Resources resources = packageManager.getResourcesForApplication(iconRes.packageName);
+            Resources resources = mPm.getResourcesForApplication(iconRes.packageName);
             if (resources != null) {
                 final int id = resources.getIdentifier(iconRes.resourceName, null, null);
                 // do not stamp old legacy shortcuts as the app may have already forgotten about it
-                return createBadgedIconBitmap(resources.getDrawableForDensity(
-                        id, LauncherAppState.getIDP(context).fillResIconDpi),
+                return createBadgedIconBitmap(
+                        resources.getDrawableForDensity(id, mFillResIconDpi),
                         Process.myUserHandle() /* only available on primary user */,
-                        context,
                         0 /* do not apply legacy treatment */);
             }
         } catch (Exception e) {
@@ -93,13 +159,12 @@
     /**
      * Returns a bitmap which is of the appropriate size to be displayed as an icon
      */
-    public static BitmapInfo createIconBitmap(Bitmap icon, Context context) {
-        final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
-        if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
+    public BitmapInfo createIconBitmap(Bitmap icon) {
+        if (mIconBitmapSize == icon.getWidth() && mIconBitmapSize == icon.getHeight()) {
             return BitmapInfo.fromBitmap(icon);
         }
         return BitmapInfo.fromBitmap(
-                createIconBitmap(new BitmapDrawable(context.getResources(), icon), context, 1f));
+                createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f));
     }
 
     /**
@@ -107,51 +172,24 @@
      * view or workspace. The icon is badged for {@param user}.
      * The bitmap is also visually normalized with other icons.
      */
-    public static BitmapInfo createBadgedIconBitmap(
-            Drawable icon, UserHandle user, Context context, int iconAppTargetSdk) {
-
-        IconNormalizer normalizer;
-        float scale = 1f;
-        if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
-            normalizer = IconNormalizer.getInstance(context);
-            if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
-                boolean[] outShape = new boolean[1];
-                AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
-                        context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
-                dr.setBounds(0, 0, 1, 1);
-                scale = normalizer.getScale(icon, null, dr.getIconMask(), outShape);
-                if (FeatureFlags.LEGACY_ICON_TREATMENT &&
-                        !outShape[0]){
-                    Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
-                    if (wrappedIcon != icon) {
-                        icon = wrappedIcon;
-                        scale = normalizer.getScale(icon, null, null, null);
-                    }
-                }
-            } else {
-                scale = normalizer.getScale(icon, null, null, null);
-            }
-        }
-        Bitmap bitmap = createIconBitmap(icon, context, scale);
-        if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO &&
-                icon instanceof AdaptiveIconDrawable) {
-            synchronized (sCanvas) {
-                sCanvas.setBitmap(bitmap);
-                ShadowGenerator.getInstance(context).recreateIcon(
-                        Bitmap.createBitmap(bitmap), sCanvas);
-                sCanvas.setBitmap(null);
-            }
+    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk) {
+        float[] scale = new float[1];
+        icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, null, scale);
+        Bitmap bitmap = createIconBitmap(icon, scale[0]);
+        if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
+            mCanvas.setBitmap(bitmap);
+            getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
+            mCanvas.setBitmap(null);
         }
 
         final Bitmap result;
         if (user != null && !Process.myUserHandle().equals(user)) {
             BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
-            Drawable badged = context.getPackageManager().getUserBadgedIcon(
-                    drawable, user);
+            Drawable badged = mPm.getUserBadgedIcon(drawable, user);
             if (badged instanceof BitmapDrawable) {
                 result = ((BitmapDrawable) badged).getBitmap();
             } else {
-                result = createIconBitmap(badged, context, 1f);
+                result = createIconBitmap(badged, 1f);
             }
         } else {
             result = bitmap;
@@ -163,170 +201,134 @@
      * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
      * normalized with other icons and has enough spacing to add shadow.
      */
-    public static Bitmap createScaledBitmapWithoutShadow(
-            Drawable icon, Context context, int iconAppTargetSdk) {
+    public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) {
         RectF iconBounds = new RectF();
-        IconNormalizer normalizer;
+        float[] scale = new float[1];
+        icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, iconBounds, scale);
+        return createIconBitmap(icon,
+                Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
+    }
+
+    private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, int iconAppTargetSdk,
+            RectF outIconBounds, float[] outScale) {
         float scale = 1f;
-        if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
-            normalizer = IconNormalizer.getInstance(context);
-            if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
-                boolean[] outShape = new boolean[1];
-                AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
-                        context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
-                dr.setBounds(0, 0, 1, 1);
-                scale = normalizer.getScale(icon, iconBounds, dr.getIconMask(), outShape);
-                if (Utilities.ATLEAST_OREO && FeatureFlags.LEGACY_ICON_TREATMENT &&
-                        !outShape[0]) {
-                    Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
-                    if (wrappedIcon != icon) {
-                        icon = wrappedIcon;
-                        scale = normalizer.getScale(icon, iconBounds, null, null);
-                    }
-                }
-            } else {
-                scale = normalizer.getScale(icon, iconBounds, null, null);
+        if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
+            boolean[] outShape = new boolean[1];
+            AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
+                    mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
+            dr.setBounds(0, 0, 1, 1);
+            scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
+            if (Utilities.ATLEAST_OREO && !outShape[0] && !(icon instanceof AdaptiveIconDrawable)) {
+                FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
+                fsd.setDrawable(icon);
+                fsd.setScale(scale);
+                icon = dr;
+                scale = getNormalizer().getScale(icon, outIconBounds, null, null);
             }
-
+        } else {
+            scale = getNormalizer().getScale(icon, outIconBounds, null, null);
         }
-        scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
-        return createIconBitmap(icon, context, scale);
+
+        outScale[0] = scale;
+        return icon;
     }
 
     /**
      * Adds the {@param badge} on top of {@param target} using the badge dimensions.
      */
-    public static void badgeWithDrawable(Bitmap target, Drawable badge, Context context) {
-        synchronized (sCanvas) {
-            sCanvas.setBitmap(target);
-            badgeWithDrawable(sCanvas, badge, context);
-            sCanvas.setBitmap(null);
-        }
+    public void badgeWithDrawable(Bitmap target, Drawable badge) {
+        mCanvas.setBitmap(target);
+        badgeWithDrawable(mCanvas, badge);
+        mCanvas.setBitmap(null);
     }
 
     /**
      * Adds the {@param badge} on top of {@param target} using the badge dimensions.
      */
-    private static void badgeWithDrawable(Canvas target, Drawable badge, Context context) {
-        int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
-        int iconSize = LauncherAppState.getIDP(context).iconBitmapSize;
-        badge.setBounds(iconSize - badgeSize, iconSize - badgeSize, iconSize, iconSize);
+    private void badgeWithDrawable(Canvas target, Drawable badge) {
+        int badgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
+        badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
+                mIconBitmapSize, mIconBitmapSize);
         badge.draw(target);
     }
 
     /**
      * @param scale the scale to apply before drawing {@param icon} on the canvas
      */
-    private static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
-        synchronized (sCanvas) {
-            final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
-            int width = iconBitmapSize;
-            int height = iconBitmapSize;
+    private Bitmap createIconBitmap(Drawable icon, float scale) {
+        int width = mIconBitmapSize;
+        int height = mIconBitmapSize;
 
-            if (icon instanceof PaintDrawable) {
-                PaintDrawable painter = (PaintDrawable) icon;
-                painter.setIntrinsicWidth(width);
-                painter.setIntrinsicHeight(height);
-            } else if (icon instanceof BitmapDrawable) {
-                // Ensure the bitmap has a density.
-                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
-                Bitmap bitmap = bitmapDrawable.getBitmap();
-                if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
-                    bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
-                }
+        if (icon instanceof PaintDrawable) {
+            PaintDrawable painter = (PaintDrawable) icon;
+            painter.setIntrinsicWidth(width);
+            painter.setIntrinsicHeight(height);
+        } else if (icon instanceof BitmapDrawable) {
+            // Ensure the bitmap has a density.
+            BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+            Bitmap bitmap = bitmapDrawable.getBitmap();
+            if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
+                bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
             }
-
-            int sourceWidth = icon.getIntrinsicWidth();
-            int sourceHeight = icon.getIntrinsicHeight();
-            if (sourceWidth > 0 && sourceHeight > 0) {
-                // Scale the icon proportionally to the icon dimensions
-                final float ratio = (float) sourceWidth / sourceHeight;
-                if (sourceWidth > sourceHeight) {
-                    height = (int) (width / ratio);
-                } else if (sourceHeight > sourceWidth) {
-                    width = (int) (height * ratio);
-                }
-            }
-            // no intrinsic size --> use default size
-            int textureWidth = iconBitmapSize;
-            int textureHeight = iconBitmapSize;
-
-            Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
-                    Bitmap.Config.ARGB_8888);
-            final Canvas canvas = sCanvas;
-            canvas.setBitmap(bitmap);
-
-            final int left = (textureWidth-width) / 2;
-            final int top = (textureHeight-height) / 2;
-
-            sOldBounds.set(icon.getBounds());
-            if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
-                int offset = Math.max((int)(ShadowGenerator.BLUR_FACTOR * iconBitmapSize),
-                        Math.min(left, top));
-                int size = Math.max(width, height);
-                icon.setBounds(offset, offset, size, size);
-            } else {
-                icon.setBounds(left, top, left+width, top+height);
-            }
-            canvas.save(Canvas.MATRIX_SAVE_FLAG);
-            canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
-            icon.draw(canvas);
-            canvas.restore();
-            icon.setBounds(sOldBounds);
-            canvas.setBitmap(null);
-
-            return bitmap;
-        }
-    }
-
-    /**
-     * If the platform is running O but the app is not providing AdaptiveIconDrawable, then
-     * shrink the legacy icon and set it as foreground. Use color drawable as background to
-     * create AdaptiveIconDrawable.
-     */
-    @TargetApi(Build.VERSION_CODES.O)
-    private static Drawable wrapToAdaptiveIconDrawable(
-            Context context, Drawable drawable, float scale) {
-        if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.ATLEAST_OREO)) {
-            return drawable;
         }
 
-        try {
-            if (!(drawable instanceof AdaptiveIconDrawable)) {
-                AdaptiveIconDrawable iconWrapper = (AdaptiveIconDrawable)
-                        context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
-                FixedScaleDrawable fsd = ((FixedScaleDrawable) iconWrapper.getForeground());
-                fsd.setDrawable(drawable);
-                fsd.setScale(scale);
-                return iconWrapper;
+        int sourceWidth = icon.getIntrinsicWidth();
+        int sourceHeight = icon.getIntrinsicHeight();
+        if (sourceWidth > 0 && sourceHeight > 0) {
+            // Scale the icon proportionally to the icon dimensions
+            final float ratio = (float) sourceWidth / sourceHeight;
+            if (sourceWidth > sourceHeight) {
+                height = (int) (width / ratio);
+            } else if (sourceHeight > sourceWidth) {
+                width = (int) (height * ratio);
             }
-        } catch (Exception e) {
-            return drawable;
         }
-        return drawable;
+        // no intrinsic size --> use default size
+        int textureWidth = mIconBitmapSize;
+        int textureHeight = mIconBitmapSize;
+
+        Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
+                Bitmap.Config.ARGB_8888);
+        mCanvas.setBitmap(bitmap);
+
+        final int left = (textureWidth-width) / 2;
+        final int top = (textureHeight-height) / 2;
+
+        mOldBounds.set(icon.getBounds());
+        if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
+            int offset = Math.max((int)(BLUR_FACTOR * textureWidth), Math.min(left, top));
+            int size = Math.max(width, height);
+            icon.setBounds(offset, offset, size, size);
+        } else {
+            icon.setBounds(left, top, left+width, top+height);
+        }
+        mCanvas.save(Canvas.MATRIX_SAVE_FLAG);
+        mCanvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
+        icon.draw(mCanvas);
+        mCanvas.restore();
+        icon.setBounds(mOldBounds);
+        mCanvas.setBitmap(null);
+
+        return bitmap;
     }
 
-    public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context) {
-        return createShortcutIcon(shortcutInfo, context, true /* badged */);
+    public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo) {
+        return createShortcutIcon(shortcutInfo, true /* badged */);
     }
 
-    public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
-            boolean badged) {
-        return createShortcutIcon(shortcutInfo, context, badged, null);
+    public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, boolean badged) {
+        return createShortcutIcon(shortcutInfo, badged, null);
     }
 
-    public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
+    public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo,
             boolean badged, @Nullable Provider<Bitmap> fallbackIconProvider) {
-        LauncherAppState app = LauncherAppState.getInstance(context);
-        Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
-                .getShortcutIconDrawable(shortcutInfo,
-                        app.getInvariantDeviceProfile().fillResIconDpi);
-        IconCache cache = app.getIconCache();
+        Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext)
+                .getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
+        IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
 
         Bitmap unbadgedBitmap = null;
         if (unbadgedDrawable != null) {
-            unbadgedBitmap = LauncherIcons.createScaledBitmapWithoutShadow(
-                    unbadgedDrawable, context, 0);
+            unbadgedBitmap = createScaledBitmapWithoutShadow(unbadgedDrawable, 0);
         } else {
             if (fallbackIconProvider != null) {
                 unbadgedBitmap = fallbackIconProvider.get();
@@ -338,28 +340,27 @@
 
         BitmapInfo result = new BitmapInfo();
         if (!badged) {
-            result.color = Themes.getColorAccent(context);
+            result.color = Themes.getColorAccent(mContext);
             result.icon = unbadgedBitmap;
             return result;
         }
 
-        int size = app.getInvariantDeviceProfile().iconBitmapSize;
-
         final Bitmap unbadgedfinal = unbadgedBitmap;
         final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache);
 
         result.color = badge.iconColor;
-        result.icon = UiFactory.createFromRenderer(size, size, false, (c) -> {
-            ShadowGenerator.getInstance(context).recreateIcon(unbadgedfinal, c);
-            badgeWithDrawable(c, new FastBitmapDrawable(badge), context);
+        result.icon = UiFactory.createFromRenderer(mIconBitmapSize, mIconBitmapSize, false, (c) -> {
+            getShadowGenerator().recreateIcon(unbadgedfinal, c);
+            badgeWithDrawable(c, new FastBitmapDrawable(badge));
         });
         return result;
     }
 
-    public static ItemInfoWithIcon getShortcutInfoBadge(
-            ShortcutInfoCompat shortcutInfo, IconCache cache) {
+    public ItemInfoWithIcon getShortcutInfoBadge(ShortcutInfoCompat shortcutInfo, IconCache cache) {
         ComponentName cn = shortcutInfo.getActivity();
-        if (cn != null) {
+        String badgePkg = shortcutInfo.getBadgePackage(mContext);
+        boolean hasBadgePkgSet = !badgePkg.equals(shortcutInfo.getPackage());
+        if (cn != null && !hasBadgePkgSet) {
             // Get the app info for the source activity.
             AppInfo appInfo = new AppInfo();
             appInfo.user = shortcutInfo.getUserHandle();
@@ -370,7 +371,7 @@
             cache.getTitleAndIcon(appInfo, false);
             return appInfo;
         } else {
-            PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage());
+            PackageItemInfo pkgInfo = new PackageItemInfo(badgePkg);
             cache.getTitleAndIconForApp(pkgInfo, false);
             return pkgInfo;
         }
diff --git a/src/com/android/launcher3/graphics/NinePatchDrawHelper.java b/src/com/android/launcher3/graphics/NinePatchDrawHelper.java
index 6df1ecb..fc20926 100644
--- a/src/com/android/launcher3/graphics/NinePatchDrawHelper.java
+++ b/src/com/android/launcher3/graphics/NinePatchDrawHelper.java
@@ -35,16 +35,43 @@
     private final RectF mDst = new RectF();
     public final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
 
+    /**
+     * Draws the bitmap split into three parts horizontally, with the middle part having width
+     * as {@link #EXTENSION_PX} in the center of the bitmap.
+     */
     public void draw(Bitmap bitmap, Canvas canvas, float left, float top, float right) {
-        int width = bitmap.getWidth();
         int height = bitmap.getHeight();
 
         mSrc.top = 0;
         mSrc.bottom = height;
-
         mDst.top = top;
         mDst.bottom = top + height;
+        draw3Patch(bitmap, canvas, left, right);
+    }
 
+
+    /**
+     * Draws the bitmap split horizontally into 3 parts (same as {@link #draw}) and split
+     * vertically into two parts, bottom part of size {@link #EXTENSION_PX} / 2 which is
+     * stretched vertically.
+     */
+    public void drawVerticallyStretched(Bitmap bitmap, Canvas canvas, float left, float top,
+            float right, float bottom) {
+        draw(bitmap, canvas, left, top, right);
+
+        // Draw bottom stretched region.
+        int height = bitmap.getHeight();
+        mSrc.top = height - EXTENSION_PX / 4;
+        mSrc.bottom = height;
+        mDst.top = top + height;
+        mDst.bottom = bottom;
+        draw3Patch(bitmap, canvas, left, right);
+    }
+
+
+
+    private void draw3Patch(Bitmap bitmap, Canvas canvas, float left, float right) {
+        int width = bitmap.getWidth();
         int halfWidth = width / 2;
 
         // Draw left edge
diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/graphics/ShadowGenerator.java
index 96f2629..5fbf502 100644
--- a/src/com/android/launcher3/graphics/ShadowGenerator.java
+++ b/src/com/android/launcher3/graphics/ShadowGenerator.java
@@ -46,17 +46,13 @@
 
     private static final int AMBIENT_SHADOW_ALPHA = 30;
 
-    private static final Object LOCK = new Object();
-    // Singleton object guarded by {@link #LOCK}
-    private static ShadowGenerator sShadowGenerator;
-
     private final int mIconSize;
 
     private final Paint mBlurPaint;
     private final Paint mDrawPaint;
     private final BlurMaskFilter mDefaultBlurMaskFilter;
 
-    private ShadowGenerator(Context context) {
+    public ShadowGenerator(Context context) {
         mIconSize = LauncherAppState.getIDP(context).iconBitmapSize;
         mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
         mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
@@ -86,18 +82,6 @@
         out.drawBitmap(icon, 0, 0, mDrawPaint);
     }
 
-    public static ShadowGenerator getInstance(Context context) {
-        // TODO: This currently fails as the system default icon also needs a shadow as it
-        // uses adaptive icon.
-        // Preconditions.assertNonUiThread();
-        synchronized (LOCK) {
-            if (sShadowGenerator == null) {
-                sShadowGenerator = new ShadowGenerator(context);
-            }
-        }
-        return sShadowGenerator;
-    }
-
     /**
      * Returns the minimum amount by which an icon with {@param bounds} should be scaled
      * so that the shadows do not get clipped.
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index b1d07f1..6378ea1 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -173,7 +173,9 @@
                 info.iconResource = new ShortcutIconResource();
                 info.iconResource.packageName = packageName;
                 info.iconResource.resourceName = resourceName;
-                BitmapInfo iconInfo = LauncherIcons.createIconBitmap(info.iconResource, mContext);
+                LauncherIcons li = LauncherIcons.obtain(mContext);
+                BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
+                li.recycle();
                 if (iconInfo != null) {
                     iconInfo.applyTo(info);
                     return true;
@@ -183,9 +185,8 @@
 
         // Failed to load from resource, try loading from DB.
         byte[] data = getBlob(iconIndex);
-        try {
-            LauncherIcons.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length),
-                    mContext).applyTo(info);
+        try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
+            li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)).applyTo(info);
             return true;
         } catch (Exception e) {
             Log.e(TAG, "Failed to load icon for info " + info, e);
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index b13b48a..883c33d 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -215,9 +215,10 @@
 
     public void loadUiResources() {
         if (Utilities.ATLEAST_OREO) {
-            ClickShadowView.setAdaptiveIconScaleFactor(
-                    IconNormalizer.getInstance(mApp.getContext()).getScale(
-                            new AdaptiveIconDrawable(null, null), null, null, null));
+            LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
+            ClickShadowView.setAdaptiveIconScaleFactor(li.getNormalizer()
+                    .getScale(new AdaptiveIconDrawable(null, null), null, null, null));
+            li.recycle();
         }
     }
 
@@ -476,8 +477,10 @@
                                                     ? finalInfo.iconBitmap : null;
                                         }
                                     };
-                                    LauncherIcons.createShortcutIcon(pinnedShortcut, context,
+                                    LauncherIcons li = LauncherIcons.obtain(context);
+                                    li.createShortcutIcon(pinnedShortcut,
                                             true /* badged */, fallbackIconProvider).applyTo(info);
+                                    li.recycle();
                                     if (pmHelper.isAppSuspended(
                                             pinnedShortcut.getPackage(), info.user)) {
                                         info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 18ae61b..089303e 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -192,8 +192,9 @@
                         // Update shortcuts which use iconResource.
                         if ((si.iconResource != null)
                                 && packageSet.contains(si.iconResource.packageName)) {
-                            BitmapInfo iconInfo =
-                                    LauncherIcons.createIconBitmap(si.iconResource, context);
+                            LauncherIcons li = LauncherIcons.obtain(context);
+                            BitmapInfo iconInfo = li.createIconBitmap(si.iconResource);
+                            li.recycle();
                             if (iconInfo != null) {
                                 iconInfo.applyTo(si);
                                 infoUpdated = true;
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 0b75e2c..59f3d1c 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -95,8 +95,10 @@
                     shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context);
                     // If the shortcut is pinned but no longer has an icon in the system,
                     // keep the current icon instead of reverting to the default icon.
-                    LauncherIcons.createShortcutIcon(fullDetails, context, true,
-                            Provider.of(shortcutInfo.iconBitmap)).applyTo(shortcutInfo);
+                    LauncherIcons li = LauncherIcons.obtain(context);
+                    li.createShortcutIcon(fullDetails, true, Provider.of(shortcutInfo.iconBitmap))
+                            .applyTo(shortcutInfo);
+                    li.recycle();
                     updatedShortcutInfos.add(shortcutInfo);
                 }
             }
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index b033405..9521a9e 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -94,8 +94,9 @@
                     si.updateFromDeepShortcutInfo(shortcut, context);
                     // If the shortcut is pinned but no longer has an icon in the system,
                     // keep the current icon instead of reverting to the default icon.
-                    LauncherIcons.createShortcutIcon(shortcut, context, true,
-                            Provider.of(si.iconBitmap)).applyTo(si);
+                    LauncherIcons li = LauncherIcons.obtain(context);
+                    li.createShortcutIcon(shortcut, true, Provider.of(si.iconBitmap)).applyTo(si);
+                    li.recycle();
                 } else {
                     si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
                 }
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorLandscape.java b/src/com/android/launcher3/pageindicators/PageIndicatorLandscape.java
index 2d5a4a9..d76998a 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorLandscape.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorLandscape.java
@@ -18,10 +18,16 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 
 import android.content.Context;
+import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.FrameLayout;
 
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
@@ -31,9 +37,11 @@
  * a view with as large an area as we want (for touching) while maintaining a caret of size
  * all_apps_caret_size.  Used only for the landscape layout.
  */
-public class PageIndicatorLandscape extends PageIndicator implements OnClickListener {
+public class PageIndicatorLandscape extends PageIndicator implements OnClickListener, Insettable {
     // all apps pull up handle drawable.
 
+    private final Launcher mLauncher;
+
     public PageIndicatorLandscape(Context context) {
         this(context, null);
     }
@@ -45,7 +53,8 @@
     public PageIndicatorLandscape(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         setOnClickListener(this);
-        setOnFocusChangeListener(Launcher.getLauncher(context).mFocusHandler);
+        mLauncher = Launcher.getLauncher(context);
+        setOnFocusChangeListener(mLauncher.mFocusHandler);
     }
 
     @Override
@@ -57,4 +66,21 @@
             l.getStateManager().goToState(ALL_APPS);
         }
     }
+
+    @Override
+    public void setInsets(Rect insets) {
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        if (insets.left > insets.right) {
+            lp.leftMargin = grid.hotseatBarSidePaddingPx;
+            lp.rightMargin = insets.right;
+            lp.gravity =  Gravity.RIGHT | Gravity.BOTTOM;
+        } else {
+            lp.leftMargin = insets.left;
+            lp.rightMargin = grid.hotseatBarSidePaddingPx;
+            lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
+        }
+        lp.bottomMargin = grid.workspacePadding.bottom;
+        setLayoutParams(lp);
+    }
 }
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorLine.java b/src/com/android/launcher3/pageindicators/PageIndicatorLine.java
index 6c4b898..8c9642c 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorLine.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorLine.java
@@ -9,12 +9,17 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.AttributeSet;
 import android.util.Property;
+import android.view.Gravity;
 import android.view.ViewConfiguration;
+import android.widget.FrameLayout;
 
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -25,7 +30,7 @@
  *
  * The fraction is 1 / number of pages and the position is based on the progress of the page scroll.
  */
-public class PageIndicatorLine extends PageIndicator {
+public class PageIndicatorLine extends PageIndicator implements Insettable {
 
     private static final int LINE_ANIMATE_DURATION = ViewConfiguration.getScrollBarFadeDuration();
     private static final int LINE_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay();
@@ -39,6 +44,7 @@
     private ValueAnimator[] mAnimators = new ValueAnimator[3];
 
     private final Handler mDelayedLineFadeHandler = new Handler(Looper.getMainLooper());
+    private final Launcher mLauncher;
 
     private boolean mShouldAutoHide = true;
 
@@ -51,7 +57,6 @@
     private int mCurrentScroll;
     private int mTotalScroll;
     private Paint mLinePaint;
-    private Launcher mLauncher;
     private final int mLineHeight;
 
     private static final Property<PageIndicatorLine, Integer> PAINT_ALPHA
@@ -224,4 +229,14 @@
         mAnimators[animatorIndex].setDuration(LINE_ANIMATE_DURATION);
         mAnimators[animatorIndex].start();
     }
+
+    @Override
+    public void setInsets(Rect insets) {
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+        lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+        lp.height = grid.pageIndicatorSizePx;
+        lp.bottomMargin = grid.hotseatBarSizePx + insets.bottom;
+        setLayoutParams(lp);
+    }
 }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index cedf291..f90abb4 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -68,7 +68,6 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.graphics.TriangleShape;
 import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.notification.NotificationInfo;
@@ -650,7 +649,7 @@
                 // reopen the container to ensure measurements etc. all work out. While this could
                 // be quite janky, in practice the user would typically see a small flicker as the
                 // animation restarts partway through, and this is a very rare edge case anyway.
-                ((PopupContainerWithArrow) getParent()).close(false);
+                close(false);
                 PopupContainerWithArrow.showForIcon(mOriginalIcon);
             }
         } else if (onClickListener == null && widgetsView != null) {
@@ -658,7 +657,7 @@
             if (mSystemShortcutContainer != this) {
                 mSystemShortcutContainer.removeView(widgetsView);
             } else {
-                ((PopupContainerWithArrow) getParent()).close(false);
+                close(false);
                 PopupContainerWithArrow.showForIcon(mOriginalIcon);
             }
         }
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 4adfb7c..b295bb2 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -148,8 +148,9 @@
                 final ShortcutInfoCompat shortcut = shortcuts.get(i);
                 final ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
                 // Use unbadged icon for the menu.
-                LauncherIcons.createShortcutIcon(shortcut, launcher, false /* badged */)
-                        .applyTo(si);
+                LauncherIcons li = LauncherIcons.obtain(launcher);
+                li.createShortcutIcon(shortcut, false /* badged */).applyTo(si);
+                li.recycle();
                 si.rank = i;
 
                 final DeepShortcutView view = shortcutViews.get(i);
diff --git a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
index 9c91c87..325777d 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
@@ -18,11 +18,14 @@
 
 import android.annotation.TargetApi;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
 import android.os.Build;
 import android.os.UserHandle;
 
+import com.android.launcher3.R;
+
 /**
  * Wrapper class for {@link android.content.pm.ShortcutInfo}, representing deep shortcuts into apps.
  *
@@ -31,8 +34,8 @@
 @TargetApi(Build.VERSION_CODES.N)
 public class ShortcutInfoCompat {
     private static final String INTENT_CATEGORY = "com.android.launcher3.DEEP_SHORTCUT";
+    private static final String EXTRA_BADGEPKG = "badge_package";
     public static final String EXTRA_SHORTCUT_ID = "shortcut_id";
-
     private ShortcutInfo mShortcutInfo;
 
     public ShortcutInfoCompat(ShortcutInfo shortcutInfo) {
@@ -57,6 +60,15 @@
         return mShortcutInfo.getPackage();
     }
 
+    public String getBadgePackage(Context context) {
+        String whitelistedPkg = context.getString(R.string.shortcutinfocompat_badgepkg_whitelist);
+        if (whitelistedPkg.equals(getPackage())
+                && mShortcutInfo.getExtras().containsKey(EXTRA_BADGEPKG)) {
+            return mShortcutInfo.getExtras().getString(EXTRA_BADGEPKG);
+        }
+        return getPackage();
+    }
+
     public String getId() {
         return mShortcutInfo.getId();
     }
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index da656db..6d584cd 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -43,24 +43,29 @@
     private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500;
 
     public SpringLoadedState(int id) {
-        super(id, ContainerType.OVERVIEW, SPRING_LOADED_TRANSITION_MS, 1f, STATE_FLAGS);
+        super(id, ContainerType.OVERVIEW, SPRING_LOADED_TRANSITION_MS, STATE_FLAGS);
     }
 
     @Override
     public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
         DeviceProfile grid = launcher.getDeviceProfile();
         Workspace ws = launcher.getWorkspace();
-        if (grid.isVerticalBarLayout() || ws.getChildCount() == 0) {
+        if (ws.getChildCount() == 0) {
             return super.getWorkspaceScaleAndTranslation(launcher);
         }
 
+        if (grid.isVerticalBarLayout()) {
+            float scale = grid.workspaceSpringLoadShrinkFactor;
+            return new float[] {scale, 0, 0};
+        }
+
         float scale = grid.workspaceSpringLoadShrinkFactor;
         Rect insets = launcher.getDragLayer().getInsets();
 
         float scaledHeight = scale * ws.getNormalChildHeight();
         float shrunkTop = insets.top + grid.dropTargetBarSizePx;
-        float shrunkBottom = ws.getViewportHeight() - insets.bottom
-                - grid.getWorkspacePadding(null).bottom
+        float shrunkBottom = ws.getMeasuredHeight() - insets.bottom
+                - grid.workspacePadding.bottom
                 - grid.workspaceSpringLoadedBottomSpace;
         float totalShrunkSpace = shrunkBottom - shrunkTop;
 
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
index ff5f64c..df34885 100644
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ b/src/com/android/launcher3/touch/SwipeDetector.java
@@ -286,6 +286,16 @@
         }
     }
 
+    /**
+     * Returns if the start drag was towards the positive direction or negative.
+     *
+     * @see #setDetectableScrollConditions(int, boolean)
+     * @see #DIRECTION_BOTH
+     */
+    public boolean wasInitialTouchPositive() {
+        return mSubtractDisplacement < 0;
+    }
+
     private boolean reportDragging() {
         if (mDisplacement != mLastDisplacement) {
             if (DBG) {
diff --git a/src/com/android/launcher3/util/VerticalSwipeController.java b/src/com/android/launcher3/util/VerticalSwipeController.java
index 7b1632c..5d47cd2 100644
--- a/src/com/android/launcher3/util/VerticalSwipeController.java
+++ b/src/com/android/launcher3/util/VerticalSwipeController.java
@@ -35,6 +35,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SwipeDetector.Direction;
 
 import java.util.ArrayList;
 
@@ -56,6 +57,7 @@
     protected final Launcher mLauncher;
     private final SwipeDetector mDetector;
     private final LauncherState mBaseState;
+    private final LauncherState mTargetState;
 
     private boolean mNoIntercept;
 
@@ -66,12 +68,18 @@
     // Ratio of transition process [0, 1] to drag displacement (px)
     private float mProgressMultiplier;
 
-    private SpringAnimationHandler[] mSpringHandlers;
+    protected SpringAnimationHandler[] mSpringHandlers;
 
     public VerticalSwipeController(Launcher l, LauncherState baseState) {
+        this(l, baseState, ALL_APPS, SwipeDetector.VERTICAL);
+    }
+
+    public VerticalSwipeController(
+            Launcher l, LauncherState baseState, LauncherState targetState, Direction dir) {
         mLauncher = l;
-        mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
+        mDetector = new SwipeDetector(l, this, dir);
         mBaseState = baseState;
+        mTargetState = targetState;
     }
 
     private boolean canInterceptTouch(MotionEvent ev) {
@@ -96,7 +104,7 @@
         }
     }
 
-    private void initSprings() {
+    protected void initSprings() {
         AllAppsContainerView appsView = mLauncher.getAppsView();
 
         SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
@@ -178,12 +186,13 @@
             long maxAccuracy = (long) (2 * range);
 
             // Build current animation
-            mToState = mLauncher.isInState(ALL_APPS) ? mBaseState : ALL_APPS;
+            mToState = mLauncher.isInState(mTargetState) ? mBaseState : mTargetState;
             mCurrentAnimation = mLauncher.getStateManager()
                     .createAnimationToNewWorkspace(mToState, maxAccuracy);
             mCurrentAnimation.getTarget().addListener(this);
             mStartProgress = 0;
-            mProgressMultiplier = (mLauncher.isInState(ALL_APPS) ? 1 : -1) / range;
+            mProgressMultiplier =
+                    (mLauncher.isInState(mTargetState) ^ isTransitionFlipped() ? 1 : -1) / range;
             mCurrentAnimation.dispatchOnStart();
         } else {
             mCurrentAnimation.pause();
@@ -195,7 +204,11 @@
         }
     }
 
-    private float getShiftRange() {
+    protected boolean isTransitionFlipped() {
+        return false;
+    }
+
+    protected float getShiftRange() {
         return mLauncher.getAllAppsController().getShiftRange();
     }
 
@@ -213,27 +226,25 @@
         final float progress = mCurrentAnimation.getProgressFraction();
 
         if (fling) {
-            if (velocity < 0) {
-                targetState = ALL_APPS;
-                animationDuration = SwipeDetector.calculateDuration(velocity,
-                        mToState == ALL_APPS ? (1 - progress) : progress);
+            if (velocity < 0 ^ isTransitionFlipped()) {
+                targetState = mTargetState;
             } else {
                 targetState = mBaseState;
-                animationDuration = SwipeDetector.calculateDuration(velocity,
-                        mToState == ALL_APPS ? progress : (1 - progress));
             }
+            animationDuration = SwipeDetector.calculateDuration(velocity,
+                    mToState == targetState ? (1 - progress) : progress);
             // snap to top or bottom using the release velocity
         } else {
             if (progress > SUCCESS_TRANSITION_PROGRESS) {
                 targetState = mToState;
                 animationDuration = SwipeDetector.calculateDuration(velocity, 1 - progress);
             } else {
-                targetState = mToState == ALL_APPS ? mBaseState : ALL_APPS;
+                targetState = mToState == mTargetState ? mBaseState : mTargetState;
                 animationDuration = SwipeDetector.calculateDuration(velocity, progress);
             }
         }
 
-        if (fling && targetState == ALL_APPS) {
+        if (fling && targetState == mTargetState) {
             for (SpringAnimationHandler h : mSpringHandlers) {
                 // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
                 h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
diff --git a/src/com/android/launcher3/views/AllAppsScrim.java b/src/com/android/launcher3/views/AllAppsScrim.java
index d1354ad..5d39adb 100644
--- a/src/com/android/launcher3/views/AllAppsScrim.java
+++ b/src/com/android/launcher3/views/AllAppsScrim.java
@@ -20,20 +20,24 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Rect;
 import android.support.v4.graphics.ColorUtils;
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.dynamicui.WallpaperColorInfo;
+import com.android.launcher3.dynamicui.WallpaperColorInfo.OnChangeListener;
 import com.android.launcher3.graphics.NinePatchDrawHelper;
 import com.android.launcher3.graphics.ShadowGenerator;
 import com.android.launcher3.util.Themes;
 
 import static com.android.launcher3.graphics.NinePatchDrawHelper.EXTENSION_PX;
 
-public class AllAppsScrim extends View implements WallpaperColorInfo.OnChangeListener {
+public class AllAppsScrim extends View implements OnChangeListener, Insettable {
 
     private static final int MAX_ALPHA = 235;
     private static final int MIN_ALPHA_PORTRAIT = 100;
@@ -42,6 +46,9 @@
     protected final WallpaperColorInfo mWallpaperColorInfo;
     private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 
+    private final Rect mPadding = new Rect();
+    private final Rect mInsets = new Rect();
+    private final DeviceProfile mGrid;
     private final float mRadius;
     private final int mMinAlpha;
     private final int mAlphaRange;
@@ -74,7 +81,8 @@
         mShadowBlur = getResources().getDimension(R.dimen.all_apps_scrim_blur);
 
         Launcher launcher = Launcher.getLauncher(context);
-        mFillAlpha = mMinAlpha = launcher.getDeviceProfile().isVerticalBarLayout()
+        mGrid = launcher.getDeviceProfile();
+        mFillAlpha = mMinAlpha = mGrid.isVerticalBarLayout()
                 ? MIN_ALPHA_LANDSCAPE : MIN_ALPHA_PORTRAIT;
         mAlphaRange = MAX_ALPHA - mMinAlpha;
         mShadowBitmap = generateShadowBitmap();
@@ -90,11 +98,12 @@
         builder.shadowBlur = mShadowBlur;
 
         // Create the bitmap such that only the top half is drawn in the bitmap.
-        int bitmapHeight = Math.round(curveBot);
-        int bitmapWidth = bitmapHeight + EXTENSION_PX;
+        int bitmapWidth = 2 * Math.round(curveBot) + EXTENSION_PX;
+        int bitmapHeight = bitmapWidth / 2;
         Bitmap result = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
 
-        builder.bounds.set(0, mShadowBlur, bitmapWidth, 2 * curveBot + EXTENSION_PX);
+        float fullSize = 2 * curveBot + EXTENSION_PX - mShadowBlur;
+        builder.bounds.set(mShadowBlur, mShadowBlur, fullSize, fullSize);
         builder.drawShadow(new Canvas(result));
         return result;
     }
@@ -126,10 +135,20 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        float edgeTop = getHeight() + mTranslateY - mDrawHeight;
+        float edgeTop = getHeight() + mTranslateY - mDrawHeight + mPadding.top;
+        float edgeRight = getWidth() - mPadding.right;
 
-        mShadowHelper.draw(mShadowBitmap, canvas, 0, edgeTop - mShadowBlur, getWidth());
-        canvas.drawRoundRect(0, edgeTop, getWidth(),
+        if (mPadding.left > 0 || mPadding.right > 0) {
+            mShadowHelper.drawVerticallyStretched(mShadowBitmap, canvas,
+                    mPadding.left - mShadowBlur,
+                    edgeTop - mShadowBlur,
+                    edgeRight + mShadowBlur,
+                    getHeight());
+        } else {
+            mShadowHelper.draw(mShadowBitmap, canvas, mPadding.left - mShadowBlur,
+                    edgeTop - mShadowBlur, edgeRight + mShadowBlur);
+        }
+        canvas.drawRoundRect(mPadding.left, edgeTop, edgeRight,
                 getHeight() + mRadius, mRadius, mRadius, mFillPaint);
     }
 
@@ -145,4 +164,20 @@
     public void setDrawRegion(float height) {
         mDrawHeight = height;
     }
+
+    @Override
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+        if (mGrid.isVerticalBarLayout()) {
+            mPadding.set(mGrid.workspacePadding);
+            mPadding.bottom = 0;
+            mPadding.left += mInsets.left;
+            mPadding.top = mInsets.top;
+            mPadding.right += mInsets.right;
+        } else {
+            float scrimMargin = getResources().getDimension(R.dimen.all_apps_scrim_margin);
+            setDrawRegion(mGrid.hotseatBarSizePx + insets.bottom + scrimMargin);
+        }
+        invalidate();
+    }
 }
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index c5cf5e2..aa5b785 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -113,7 +113,9 @@
         } else {
             PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
             Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache());
-            preview = LauncherIcons.createScaledBitmapWithoutShadow(icon, launcher, 0);
+            LauncherIcons li = LauncherIcons.obtain(launcher);
+            preview = li.createScaledBitmapWithoutShadow(icon, 0);
+            li.recycle();
             scale = ((float) launcher.getDeviceProfile().iconSizePx) / preview.getWidth();
 
             dragOffset = new Point(previewPadding / 2, previewPadding / 2);
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index a40ea1b..a1bfe88 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -42,8 +42,6 @@
     private static final long FADE_IN_DURATION = 150;
     private static final float VERTICAL_START_POSITION = 0.3f;
 
-    private static final Rect sTempRect = new Rect();
-
     private final Rect mInsets = new Rect();
 
     private final WidgetsListAdapter mAdapter;
@@ -115,8 +113,8 @@
             mGradientView.setVisibility(View.INVISIBLE);
             widthUsed = 0;
         } else {
-            mLauncher.getDeviceProfile().getWorkspacePadding(sTempRect);
-            widthUsed = Math.max(sTempRect.left + sTempRect.right,
+            Rect padding = mLauncher.getDeviceProfile().workspacePadding;
+            widthUsed = Math.max(padding.left + padding.right,
                     2 * (mInsets.left + mInsets.right));
         }
 
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
index 485e97b..24236d8 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
@@ -43,7 +43,7 @@
     };
 
     public AllAppsState(int id) {
-        super(id, ContainerType.ALLAPPS, ALL_APPS_TRANSITION_MS, 0f, STATE_FLAGS);
+        super(id, ContainerType.ALLAPPS, ALL_APPS_TRANSITION_MS, STATE_FLAGS);
     }
 
     @Override
@@ -77,4 +77,9 @@
     public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
         return PAGE_ALPHA_PROVIDER;
     }
+
+    @Override
+    public float getVerticalProgress(Launcher launcher) {
+        return 0f;
+    }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
index 19967ae..d18901d 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
@@ -40,7 +40,7 @@
             FLAG_DISABLE_PAGE_CLIPPING;
 
     public OverviewState(int id) {
-        super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, 1f, STATE_FLAGS);
+        super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
     }
 
     @Override
@@ -51,11 +51,10 @@
 
         int overviewButtonBarHeight = OverviewPanel.getButtonBarHeight(launcher);
         int scaledHeight = (int) (SCALE_FACTOR * ws.getNormalChildHeight());
-        Rect workspacePadding = grid.getWorkspacePadding(null);
-        int workspaceTop = insets.top + workspacePadding.top;
-        int workspaceBottom = ws.getViewportHeight() - insets.bottom - workspacePadding.bottom;
+        int workspaceTop = insets.top + grid.workspacePadding.top;
+        int workspaceBottom = ws.getHeight() - insets.bottom - grid.workspacePadding.bottom;
         int overviewTop = insets.top;
-        int overviewBottom = ws.getViewportHeight() - insets.bottom - overviewButtonBarHeight;
+        int overviewBottom = ws.getHeight() - insets.bottom - overviewButtonBarHeight;
         int workspaceOffsetTopEdge =
                 workspaceTop + ((workspaceBottom - workspaceTop) - scaledHeight) / 2;
         int overviewOffsetTopEdge = overviewTop + (overviewBottom - overviewTop - scaledHeight) / 2;
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index fc81e80..2ea10c2 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -56,4 +56,6 @@
         renderer.render(new Canvas(result));
         return result;
     }
+
+    public static void resetOverview(Launcher launcher) { }
 }