Merge "Fade away half-screen assistant on swipe-to-recents gesture." into ub-launcher3-master
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6ef7828..c3ddfd6 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -71,7 +71,7 @@
             android:clearTaskOnLaunch="true"
             android:stateNotNeeded="true"
             android:windowSoftInputMode="adjustPan"
-            android:screenOrientation="nosensor"
+            android:screenOrientation="unspecified"
             android:configChanges="keyboard|keyboardHidden|navigation"
             android:resizeableActivity="true"
             android:resumeWhilePausing="true"
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 91f74a6..b97669b 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
 import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
@@ -35,6 +37,7 @@
 import android.content.res.Resources;
 import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Handler;
 import android.util.Log;
@@ -46,6 +49,7 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.InsettableFrameLayout.LayoutParams;
 import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -225,7 +229,7 @@
         // Resolve the opening task id
         int openingTaskId = -1;
         for (RemoteAnimationTargetCompat target : targets) {
-            if (target.mode == RemoteAnimationTargetCompat.MODE_OPENING) {
+            if (target.mode == MODE_OPENING) {
                 openingTaskId = target.taskId;
                 break;
             }
@@ -245,8 +249,36 @@
         }
 
         // Found a visible recents task that matches the opening app, lets launch the app from there
-        return new LauncherTransitionAnimator(getRecentsLauncherAnimator(recentsView, taskView),
-                getRecentsWindowAnimator(taskView, targets));
+        Animator launcherAnim;
+        AnimatorListenerAdapter windowAnimEndListener;
+        boolean launcherClosing = launcherIsATargetWithMode(targets, MODE_CLOSING);
+        if (launcherClosing) {
+            launcherAnim = getRecentsLauncherAnimator(recentsView, taskView);
+            windowAnimEndListener = new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    // Make sure recents gets fixed up by resetting task alphas and scales, etc.
+                    mLauncher.getStateManager().reapplyState();
+                }
+            };
+        } else {
+            AnimatorPlaybackController controller =
+                    mLauncher.getStateManager()
+                            .createAnimationToNewWorkspace(NORMAL, RECENTS_LAUNCH_DURATION);
+            controller.dispatchOnStart();
+            launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
+            windowAnimEndListener = new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mLauncher.getStateManager().goToState(NORMAL, false);
+                }
+            };
+        }
+
+        MutableBoolean skipLauncherChanges = new MutableBoolean(!launcherClosing);
+        Animator windowAnim = getRecentsWindowAnimator(taskView, skipLauncherChanges, targets);
+        windowAnim.addListener(windowAnimEndListener);
+        return new LauncherTransitionAnimator(launcherAnim, windowAnim, skipLauncherChanges);
     }
 
     /**
@@ -316,7 +348,7 @@
         launcherAnimator.play(allAppsSlideOut);
 
         Workspace workspace = mLauncher.getWorkspace();
-        float[] workspaceScaleAndTranslation = LauncherState.NORMAL
+        float[] workspaceScaleAndTranslation = NORMAL
                 .getWorkspaceScaleAndTranslation(mLauncher);
         Animator recenterWorkspace = LauncherAnimUtils.ofPropertyValuesHolder(
                 workspace, new PropertyListBuilder()
@@ -336,7 +368,7 @@
      * @return Animator that controls the window of the opening targets for the recents launch
      * animation.
      */
-    private ValueAnimator getRecentsWindowAnimator(TaskView v,
+    private ValueAnimator getRecentsWindowAnimator(TaskView v, MutableBoolean skipLauncherChanges,
             RemoteAnimationTargetCompat[] targets) {
         Rect taskViewBounds = new Rect();
         mDragLayer.getDescendantRectRelativeToSelf(v, taskViewBounds);
@@ -372,13 +404,15 @@
                 final float percent = animation.getAnimatedFraction();
                 TaskWindowBounds tw = recentsInterpolator.interpolate(percent);
 
-                v.setScaleX(tw.taskScale);
-                v.setScaleY(tw.taskScale);
-                v.setTranslationX(tw.taskX);
-                v.setTranslationY(tw.taskY);
-                // Defer fading out the view until after the app window gets faded in
-                v.setAlpha(getValue(1f, 0f, 75, 75,
-                        appAnimator.getDuration() * percent, Interpolators.LINEAR));
+                if (!skipLauncherChanges.value) {
+                    v.setScaleX(tw.taskScale);
+                    v.setScaleY(tw.taskScale);
+                    v.setTranslationX(tw.taskX);
+                    v.setTranslationY(tw.taskY);
+                    // Defer fading out the view until after the app window gets faded in
+                    v.setAlpha(getValue(1f, 0f, 75, 75,
+                            appAnimator.getDuration() * percent, Interpolators.LINEAR));
+                }
 
                 matrix.setScale(tw.winScale, tw.winScale);
                 matrix.postTranslate(tw.winX, tw.winY);
@@ -400,7 +434,10 @@
                         matrix.postTranslate(target.position.x, target.position.y);
                         t.setMatrix(target.leash, matrix);
                         t.setWindowCrop(target.leash, crop);
-                        t.deferTransactionUntil(target.leash, surface, getNextFrameNumber(surface));
+
+                        if (!skipLauncherChanges.value) {
+                            t.deferTransactionUntil(target.leash, surface, frameNumber);
+                        }
                     }
                     if (isFirstFrame) {
                         t.show(target.leash);
@@ -412,13 +449,6 @@
                 isFirstFrame = false;
             }
         });
-        appAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                // Make sure recents gets fixed up by resetting task alphas and scales, etc.
-                mLauncher.getStateManager().reapplyState();
-            }
-        });
         return appAnimator;
     }
 
@@ -520,8 +550,14 @@
                 : rect.left;
         final int viewLocationTop = rect.top;
 
+        float startScale = 1f;
         if (isBubbleTextView && !isDeepShortcutTextView) {
-            ((BubbleTextView) v).getIconBounds(rect);
+            BubbleTextView btv = (BubbleTextView) v;
+            btv.getIconBounds(rect);
+            Drawable dr = btv.getIcon();
+            if (dr instanceof FastBitmapDrawable) {
+                startScale = ((FastBitmapDrawable) dr).getAnimatedScale();
+            }
         } else {
             rect.set(0, 0, rect.width(), rect.height());
         }
@@ -563,14 +599,10 @@
         float maxScaleX = mDeviceProfile.widthPx / (float) rect.width();
         float maxScaleY = mDeviceProfile.heightPx / (float) rect.height();
         float scale = Math.max(maxScaleX, maxScaleY);
-        ObjectAnimator sX = ObjectAnimator.ofFloat(mFloatingView, View.SCALE_X, 1f, scale);
-        ObjectAnimator sY = ObjectAnimator.ofFloat(mFloatingView, View.SCALE_Y, 1f, scale);
-        sX.setDuration(500);
-        sY.setDuration(500);
-        sX.setInterpolator(Interpolators.EXAGGERATED_EASE);
-        sY.setInterpolator(Interpolators.EXAGGERATED_EASE);
-        appIconAnimatorSet.play(sX);
-        appIconAnimatorSet.play(sY);
+        ObjectAnimator scaleAnim = ObjectAnimator
+                .ofFloat(mFloatingView, SCALE_PROPERTY, startScale, scale);
+        scaleAnim.setDuration(500).setInterpolator(Interpolators.EXAGGERATED_EASE);
+        appIconAnimatorSet.play(scaleAnim);
 
         // Fade out the app icon.
         ObjectAnimator alpha = ObjectAnimator.ofFloat(mFloatingView, View.ALPHA, 1f, 0f);
diff --git a/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java b/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
index aec2869..ab9234b 100644
--- a/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
+++ b/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
@@ -27,11 +27,20 @@
  */
 public class LauncherTransitionAnimator {
 
+    private final MutableBoolean mLauncherAnimCancelState;
+
     private AnimatorSet mAnimatorSet;
     private Animator mLauncherAnimator;
     private Animator mWindowAnimator;
 
     LauncherTransitionAnimator(Animator launcherAnimator, Animator windowAnimator) {
+        this(launcherAnimator, windowAnimator, new MutableBoolean(false));
+    }
+
+
+    LauncherTransitionAnimator(Animator launcherAnimator, Animator windowAnimator,
+            MutableBoolean launcherAnimCancelState) {
+        mLauncherAnimCancelState = launcherAnimCancelState;
         if (launcherAnimator != null) {
             mLauncherAnimator = launcherAnimator;
         }
@@ -50,6 +59,7 @@
 
     public void cancel() {
         mAnimatorSet.cancel();
+        mLauncherAnimCancelState.value = true;
     }
 
     public boolean isRunning() {
@@ -58,6 +68,7 @@
 
     public void finishLauncherAnimation() {
         if (mLauncherAnimator != null) {
+            mLauncherAnimCancelState.value = true;
             mLauncherAnimator.end();
         }
     }
diff --git a/quickstep/src/com/android/launcher3/MutableBoolean.java b/quickstep/src/com/android/launcher3/MutableBoolean.java
new file mode 100644
index 0000000..7538217
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/MutableBoolean.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+public class MutableBoolean {
+    public boolean value;
+
+    public MutableBoolean(boolean value) {
+        this.value = value;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
index 541c6bb..a55edfe 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
@@ -31,7 +31,6 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
@@ -143,11 +142,6 @@
     }
 
     @Override
-    protected void initSprings() {
-        mSpringHandlers = new SpringAnimationHandler[0];
-    }
-
-    @Override
     protected float getShiftRange() {
         return getShiftRange(mLauncher);
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 3f305e9..854fb4f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
 
 import android.graphics.Rect;
 import android.view.View;
@@ -74,6 +75,11 @@
     }
 
     @Override
+    public void onStateTransitionEnd(Launcher launcher) {
+        launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
+    }
+
+    @Override
     public View getFinalFocus(Launcher launcher) {
         return launcher.getOverviewPanel();
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 4542431..92d071a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -83,7 +83,8 @@
 
         // Scroll to the workspace card before changing to the NORMAL state.
         int currPage = mRecentsView.getCurrentPage();
-        if (toState == NORMAL && currPage != 0 && !config.userControlled) {
+        LauncherState fromState = mLauncher.getStateManager().getState();
+        if (fromState.overviewUi && toState == NORMAL && currPage != 0 && !config.userControlled) {
             int maxSnapDuration = PagedView.SLOW_PAGE_SNAP_ANIMATION_DURATION;
             int durationPerPage = maxSnapDuration / 10;
             int snapDuration = Math.min(maxSnapDuration, durationPerPage * currPage);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
index 2695054..c8d75dc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
@@ -19,7 +19,6 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
 import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
 
 import android.animation.Animator;
@@ -27,7 +26,6 @@
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.support.animation.SpringAnimation;
 import android.util.Log;
 import android.view.MotionEvent;
 
@@ -38,11 +36,9 @@
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
@@ -52,8 +48,6 @@
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.TouchInteractionService;
 
-import java.util.ArrayList;
-
 /**
  * Handles vertical touch gesture on the DragLayer
  */
@@ -112,8 +106,6 @@
     // Ratio of transition process [0, 1] to drag displacement (px)
     private float mProgressMultiplier;
 
-    private SpringAnimationHandler[] mSpringHandlers;
-
     public TwoStepSwipeController(Launcher l) {
         mLauncher = l;
         mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
@@ -156,29 +148,6 @@
         }
     }
 
-    private void initSprings() {
-        AllAppsContainerView appsView = mLauncher.getAppsView();
-
-        SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
-        if (handler == null) {
-            mSpringHandlers = new SpringAnimationHandler[0];
-            return;
-        }
-
-        ArrayList<SpringAnimationHandler> handlers = new ArrayList<>();
-        handlers.add(handler);
-
-        SpringAnimation searchSpring = appsView.getSearchUiManager().getSpringForFling();
-        if (searchSpring != null) {
-            SpringAnimationHandler searchHandler =
-                    new SpringAnimationHandler(Y_DIRECTION, handler.getFactory());
-            searchHandler.add(searchSpring, true /* setDefaultValues */);
-            handlers.add(searchHandler);
-        }
-
-        mSpringHandlers = handlers.toArray(new SpringAnimationHandler[handlers.size()]);
-    }
-
     @Override
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -214,10 +183,6 @@
 
             mDetector.setDetectableScrollConditions(
                     directionsToDetectScroll, ignoreSlopWhenSettling);
-
-            if (mSpringHandlers == null) {
-                initSprings();
-            }
         }
 
         if (mNoIntercept) {
@@ -230,9 +195,6 @@
 
     @Override
     public boolean onControllerTouchEvent(MotionEvent ev) {
-        for (SpringAnimationHandler h : mSpringHandlers) {
-            h.addMovement(ev);
-        }
         return mDetector.onTouchEvent(ev);
     }
 
@@ -283,10 +245,6 @@
             mDragPauseDetector.clearDisabledFlags(FLAG_OVERVIEW_DISABLED_FLING);
             updatePauseDetectorRangeFlag();
         }
-
-        for (SpringAnimationHandler h : mSpringHandlers) {
-            h.skipToEnd();
-        }
     }
 
     private float getShiftRange() {
@@ -329,13 +287,6 @@
             targetState = (progress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
         }
 
-        if (fling && targetState == ALL_APPS) {
-            for (SpringAnimationHandler h : mSpringHandlers) {
-                // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
-                h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
-            }
-        }
-
         float endProgress;
 
         if (mDragPauseDetector.isTriggered() && targetState == NORMAL) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 7f98935..a8bcb11 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -18,28 +18,25 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.PointF;
+import android.view.View;
 import android.view.View.AccessibilityDelegate;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.BitmapRenderer;
+import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RecentsView;
-import com.android.systemui.shared.recents.view.RecentsTransition;
 
 public class UiFactory {
 
     private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
             "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
 
-    public static final boolean USE_HARDWARE_BITMAP = false; // FeatureFlags.IS_DOGFOOD_BUILD;
-
     public static TouchController[] createTouchControllers(Launcher launcher) {
         if (FeatureFlags.ENABLE_TWO_SWIPE_TARGETS) {
             return new TouchController[] {
@@ -68,19 +65,21 @@
     }
 
     public static void onLauncherStateOrFocusChanged(Launcher launcher) {
-        OverviewInteractionState.setBackButtonVisible(launcher, launcher == null
-                || !launcher.isInState(NORMAL) || !launcher.hasWindowFocus());
-    }
-
-    public static Bitmap createFromRenderer(int width, int height, boolean forceSoftwareRenderer,
-            BitmapRenderer renderer) {
-        if (USE_HARDWARE_BITMAP && !forceSoftwareRenderer) {
-            return RecentsTransition.createHardwareBitmap(width, height, renderer::render);
-        } else {
-            Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-            renderer.render(new Canvas(result));
-            return result;
+        boolean shouldBackButtonBeVisible = launcher == null
+                || !launcher.isInState(NORMAL)
+                || !launcher.hasWindowFocus();
+        if (!shouldBackButtonBeVisible) {
+            // Show the back button if there is a floating view visible.
+            DragLayer dragLayer = launcher.getDragLayer();
+            for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
+                View child = dragLayer.getChildAt(i);
+                if (child instanceof AbstractFloatingView) {
+                    shouldBackButtonBeVisible = true;
+                    break;
+                }
+            }
         }
+        OverviewInteractionState.setBackButtonVisible(launcher, shouldBackButtonBeVisible);
     }
 
     public static void resetOverview(Launcher launcher) {
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 67b36b3..ede0ad2 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -19,6 +19,8 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
+import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
 import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_START_DURATION;
 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
@@ -33,7 +35,6 @@
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
-import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
 import android.graphics.Matrix;
 import android.graphics.Matrix.ScaleToFit;
@@ -234,7 +235,9 @@
         mContext = context;
         mRunningTaskId = runningTaskInfo.id;
         mTouchTimeMs = touchTimeMs;
-        mInputConsumer.registerInputConsumer();
+        // Register the input consumer on the UI thread, to ensure that it runs after any pending
+        // unregister calls
+        mMainExecutor.execute(mInputConsumer::registerInputConsumer);
         initStateCallbacks();
     }
 
@@ -364,7 +367,8 @@
 
         // For the duration of the gesture, lock the screen orientation to ensure that we do not
         // rotate mid-quickscrub
-        mLauncher.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+        mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
+
         mRecentsView = mLauncher.getOverviewPanel();
         mQuickScrubController = mRecentsView.getQuickScrubController();
         mLauncherLayoutListener = new LauncherLayoutListener(mLauncher);
@@ -760,7 +764,8 @@
         mLauncherLayoutListener.close(false);
 
         // Restore the requested orientation to the user preference after the gesture has ended
-        mLauncher.updateRequestedOrientation();
+        mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_NONE);
+
         mRecentsView.setFirstTaskIconScaledDown(false /* isScaledDown */, false /* animate */);
     }
 
diff --git a/res/drawable/all_apps_search_divider.xml b/res/drawable/bg_all_apps_searchbox.xml
similarity index 78%
rename from res/drawable/all_apps_search_divider.xml
rename to res/drawable/bg_all_apps_searchbox.xml
index 99905e4..c324927 100644
--- a/res/drawable/all_apps_search_divider.xml
+++ b/res/drawable/bg_all_apps_searchbox.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- 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.
@@ -13,8 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-    <solid android:color="?android:attr/colorAccent" />
-    <size android:height="1dp" />
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <solid android:color="?attr/popupColorPrimary" />
+    <corners android:radius="2dp" />
 </shape>
\ No newline at end of file
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 2ce6b8c..450d107 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -29,20 +29,13 @@
 
     <include layout="@layout/all_apps_rv_layout" />
 
-    <include layout="@layout/all_apps_fast_scroller" />
-
     <include layout="@layout/all_apps_floating_header" />
 
     <!-- Note: we are reusing/repurposing a system attribute for search layout, because of a
      platform bug, which prevents using custom attributes in <include> tag -->
     <include
         android:id="@id/search_container_all_apps"
-        layout="?android:attr/keyboardLayout"/>
+        layout="@layout/search_container_all_apps"/>
 
-    <View
-        android:id="@+id/nav_bar_bg"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_alignParentBottom="true"
-        android:background="?attr/allAppsNavBarScrimColor" />
+    <include layout="@layout/all_apps_fast_scroller" />
 </com.android.launcher3.allapps.AllAppsContainerView>
\ No newline at end of file
diff --git a/res/layout/all_apps_rv_layout.xml b/res/layout/all_apps_rv_layout.xml
index 3c19f8c..c353b36 100644
--- a/res/layout/all_apps_rv_layout.xml
+++ b/res/layout/all_apps_rv_layout.xml
@@ -22,5 +22,4 @@
     android:layout_below="@id/search_container_all_apps"
     android:clipToPadding="false"
     android:descendantFocusability="afterDescendants"
-    android:focusable="true"
-    android:overScrollMode="never" />
+    android:focusable="true" />
diff --git a/res/layout/deep_shortcut.xml b/res/layout/deep_shortcut.xml
index 4a3db1f..4a2ad42 100644
--- a/res/layout/deep_shortcut.xml
+++ b/res/layout/deep_shortcut.xml
@@ -35,7 +35,6 @@
         android:textColor="?android:attr/textColorPrimary"
         android:fontFamily="sans-serif"
         launcher:layoutHorizontal="true"
-        launcher:deferShadowGeneration="true"
         launcher:iconDisplay="shortcut_popup"
         launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" />
 
diff --git a/res/layout/search_container_all_apps.xml b/res/layout/search_container_all_apps.xml
index fc07002..14d7b53 100644
--- a/res/layout/search_container_all_apps.xml
+++ b/res/layout/search_container_all_apps.xml
@@ -17,53 +17,23 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@id/search_container_all_apps"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/all_apps_search_bar_height"
-    android:layout_gravity="center|top"
-    android:layout_marginBottom="-8dp"
-    android:gravity="center|bottom"
-    android:paddingLeft="@dimen/dynamic_grid_edge_margin"
-    android:paddingRight="@dimen/dynamic_grid_edge_margin"
-    android:saveEnabled="false" >
-
-    <!--
-      Note: The following relation should always be true so that the shadows are aligned properly
-      search_box_input.layout_marginBottom
-            == search_divider.layout_marginBottom
-            == - (search_container.layout_marginBottom)
-            >= 5dp
-    -->
-    <com.android.launcher3.ExtendedEditText
-        android:id="@+id/search_box_input"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/all_apps_search_bar_field_height"
-        android:layout_gravity="bottom"
-        android:layout_marginBottom="8dp"
-        android:background="@android:color/transparent"
-        android:focusableInTouchMode="true"
-        android:gravity="center"
-        android:hint="@string/all_apps_search_bar_hint"
-        android:imeOptions="actionSearch|flagNoExtractUi"
-        android:inputType="text|textNoSuggestions|textCapWords"
-        android:maxLines="1"
-        android:saveEnabled="false"
-        android:scrollHorizontally="true"
-        android:singleLine="true"
-        android:textColor="?android:attr/textColorSecondary"
-        android:textColorHint="@drawable/all_apps_search_hint"
-        android:textSize="16sp" />
-
-    <!-- This needs to be a container with a view, to simulate a scrolling effect for the header.
-         We translate the header down, and its content up by the same amount, so that it gets
-         clipped by the parent, making it look like the divider was scrolled out of the view. -->
-    <FrameLayout
-        android:id="@+id/search_divider"
-        android:layout_width="match_parent"
-        android:layout_height="1dp"
-        android:layout_gravity="bottom"
-        android:layout_marginBottom="8dp" >
-        <View
-            android:layout_width="match_parent"
-            android:layout_height="1dp"
-            android:background="@drawable/all_apps_search_divider"/>
-    </FrameLayout>
-</com.android.launcher3.allapps.search.AppsSearchContainerLayout>
\ No newline at end of file
+    android:layout_height="@dimen/all_apps_search_bar_field_height"
+    android:layout_centerHorizontal="true"
+    android:layout_gravity="top|center_horizontal"
+    android:layout_marginTop="8dp"
+    android:background="@drawable/bg_all_apps_searchbox"
+    android:elevation="1dp"
+    android:focusableInTouchMode="true"
+    android:gravity="center"
+    android:hint="@string/all_apps_search_bar_hint"
+    android:imeOptions="actionSearch|flagNoExtractUi"
+    android:inputType="text|textNoSuggestions|textCapWords"
+    android:maxLines="1"
+    android:padding="8dp"
+    android:saveEnabled="false"
+    android:scrollHorizontally="true"
+    android:singleLine="true"
+    android:textColor="?android:attr/textColorSecondary"
+    android:textColorHint="@drawable/all_apps_search_hint"
+    android:textSize="16sp"
+    android:translationY="24dp" />
\ No newline at end of file
diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml
index 1888e22..04f3d02 100644
--- a/res/layout/system_shortcut.xml
+++ b/res/layout/system_shortcut.xml
@@ -34,7 +34,6 @@
         android:fontFamily="sans-serif"
         launcher:iconDisplay="shortcut_popup"
         launcher:layoutHorizontal="true"
-        launcher:deferShadowGeneration="true"
         android:focusable="false" />
 
     <View
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index ca397bd..978b5a1 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -47,13 +47,5 @@
             android:layout_height="match_parent"
             android:layout_gravity="end"
             android:layout_marginEnd="@dimen/fastscroll_end_margin" />
-
-        <View
-            android:id="@+id/nav_bar_bg"
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_gravity="bottom"
-            android:background="?attr/allAppsNavBarScrimColor"
-            android:focusable="false"  />
     </com.android.launcher3.views.TopRoundedCornerView>
 </com.android.launcher3.widget.WidgetsFullSheet>
\ No newline at end of file
diff --git a/res/layout/widgets_list_row_view.xml b/res/layout/widgets_list_row_view.xml
index 4cd03ce..91baf7a 100644
--- a/res/layout/widgets_list_row_view.xml
+++ b/res/layout/widgets_list_row_view.xml
@@ -42,7 +42,6 @@
         android:textColor="?android:attr/textColorPrimary"
         android:textSize="16sp"
         android:textAlignment="viewStart"
-        launcher:deferShadowGeneration="true"
         launcher:iconDisplay="widget_section"
         launcher:iconSizeOverride="@dimen/widget_section_icon_size"
         launcher:layoutHorizontal="true" />
diff --git a/res/values-sw720dp/styles.xml b/res/values-sw720dp/styles.xml
index 72894dc..f322e9f 100644
--- a/res/values-sw720dp/styles.xml
+++ b/res/values-sw720dp/styles.xml
@@ -26,7 +26,6 @@
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowActionModeOverlay">true</item>
         <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
-        <item name="android:keyboardLayout">@layout/search_container_all_apps</item>
     </style>
 
     <!-- Workspace -->
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 1351dfa..64ca05e 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -44,7 +44,6 @@
             <enum name="widget_section" value="3" />
             <enum name="shortcut_popup" value="4" />
         </attr>
-        <attr name="deferShadowGeneration" format="boolean" />
         <attr name="centerVertically" format="boolean" />
     </declare-styleable>
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 1f46844..9f4233c 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -79,7 +79,7 @@
     <!-- All Apps -->
     <dimen name="all_apps_button_scale_down">0dp</dimen>
     <dimen name="all_apps_search_bar_field_height">48dp</dimen>
-    <dimen name="all_apps_search_bar_height">60dp</dimen>
+    <dimen name="all_apps_search_bar_bottom_padding">30dp</dimen>
     <dimen name="all_apps_empty_search_message_top_offset">40dp</dimen>
     <dimen name="all_apps_empty_search_bg_top_offset">144dp</dimen>
     <dimen name="all_apps_background_canvas_width">700dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d6abe22..d8b68e7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -173,12 +173,6 @@
     <string name="accessibility_action_overview">Overview</string>
 
     <!-- Strings for settings -->
-    <!-- Title for Allow Rotation setting. [CHAR LIMIT=50] -->
-    <string name="allow_rotation_title">Allow Home screen rotation</string>
-    <!-- Text explaining when the home screen will get rotated. [CHAR LIMIT=100] -->
-    <string name="allow_rotation_desc">When phone is rotated</string>
-    <!-- Text explaining that rotation is disabled in Display settings. 'Display' refers to the Display section in system settings [CHAR LIMIT=100] -->
-    <string name="allow_rotation_blocked_desc">Current Display setting doesn\'t permit rotation</string>
     <!-- Title for Notification dots setting. Tapping this will link to the system Notifications settings screen where the user can turn off notification dots globally. [CHAR LIMIT=50] -->
     <string name="icon_badging_title">Notification dots</string>
     <!-- Text to indicate that the system icon badging setting is on [CHAR LIMIT=100] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8cc4743..ac6a6b1 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -25,7 +25,6 @@
         <item name="android:windowShowWallpaper">true</item>
         <item name="android:windowNoTitle">true</item>
         <item name="android:colorEdgeEffect">#FF757575</item>
-        <item name="android:keyboardLayout">@layout/search_container_all_apps</item>
     </style>
 
     <style name="BaseLauncherThemeWithCustomAttrs" parent="@style/BaseLauncherTheme">
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 28a35b8..7bb19f3 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -37,13 +37,6 @@
         android:persistent="true"
         />
 
-    <SwitchPreference
-        android:key="pref_allowRotation"
-        android:title="@string/allow_rotation_title"
-        android:defaultValue="@bool/allow_rotation"
-        android:persistent="true"
-        />
-
     <ListPreference
         android:key="pref_override_icon_shape"
         android:title="@string/icon_shape_override_label"
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index cc13263..74b9cfa 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -33,8 +33,7 @@
  *   <li> Enable fast scroller.
  * </ul>
  */
-public abstract class BaseRecyclerView extends RecyclerView
-        implements RecyclerView.OnItemTouchListener {
+public abstract class BaseRecyclerView extends RecyclerView  {
 
     protected RecyclerViewFastScroller mScrollbar;
 
@@ -51,12 +50,6 @@
     }
 
     @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        addOnItemTouchListener(this);
-    }
-
-    @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         bindFastScrollbar();
@@ -69,40 +62,8 @@
         onUpdateScrollbar(0);
     }
 
-    /**
-     * We intercept the touch handling only to support fast scrolling when initiated from the
-     * scroll bar.  Otherwise, we fall back to the default RecyclerView touch handling.
-     */
-    @Override
-    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
-        return handleTouchEvent(ev);
-    }
-
-    @Override
-    public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
-        handleTouchEvent(ev);
-    }
-
-    /**
-     * Handles the touch event and determines whether to show the fast scroller (or updates it if
-     * it is already showing).
-     */
-    private boolean handleTouchEvent(MotionEvent ev) {
-        // 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() - mScrollbar.getLeft();
-        int top = parent.getTop() + getTop() - mScrollbar.getTop() - getScrollBarTop();
-        ev.offsetLocation(left, top);
-        try {
-            return mScrollbar.handleTouchEvent(ev);
-        } finally {
-            ev.offsetLocation(-left, -top);
-        }
-    }
-
-    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
-        // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
+    public RecyclerViewFastScroller getScrollbar() {
+        return mScrollbar;
     }
 
     public int getScrollBarTop() {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 4f50d1a..8b6d9f8 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -37,7 +36,6 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewDebug;
-import android.view.ViewParent;
 import android.widget.TextView;
 
 import com.android.launcher3.IconCache.IconLoadRequest;
@@ -48,7 +46,6 @@
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.FolderIconPreviewVerifier;
 import com.android.launcher3.graphics.DrawableFactory;
-import com.android.launcher3.graphics.HolographicOutlineHelper;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.model.PackageItemInfo;
@@ -73,13 +70,9 @@
     private final boolean mCenterVertically;
 
     private final CheckLongPressHelper mLongPressHelper;
-    private final HolographicOutlineHelper mOutlineHelper;
     private final StylusEventHelper mStylusEventHelper;
     private final float mSlop;
 
-    private Bitmap mPressedBackground;
-
-    private final boolean mDeferShadowGenerationOnTouch;
     private final boolean mLayoutHorizontal;
     private final int mIconSize;
     @ViewDebug.ExportedProperty(category = "launcher")
@@ -147,8 +140,6 @@
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.BubbleTextView, defStyle, 0);
         mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
-        mDeferShadowGenerationOnTouch =
-                a.getBoolean(R.styleable.BubbleTextView_deferShadowGeneration, false);
 
         int display = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
         int defaultIconSize = grid.iconSizePx;
@@ -173,7 +164,6 @@
         mLongPressHelper = new CheckLongPressHelper(this);
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
-        mOutlineHelper = HolographicOutlineHelper.getInstance(getContext());
         setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
 
     }
@@ -290,13 +280,6 @@
 
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
-                // So that the pressed outline is visible immediately on setStayPressed(),
-                // we pre-create it on ACTION_DOWN (it takes a small but perceptible amount of time
-                // to create it)
-                if (!mDeferShadowGenerationOnTouch && mPressedBackground == null) {
-                    mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
-                }
-
                 // If we're in a stylus button press, don't check for long press.
                 if (!mStylusEventHelper.inStylusButtonPressed()) {
                     mLongPressHelper.postCheckForLongPress();
@@ -304,12 +287,6 @@
                 break;
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP:
-                // If we've touched down and up on an item, and it's still not "pressed", then
-                // destroy the pressed outline
-                if (!isPressed()) {
-                    mPressedBackground = null;
-                }
-
                 mLongPressHelper.cancelLongPress();
                 break;
             case MotionEvent.ACTION_MOVE:
@@ -323,22 +300,6 @@
 
     void setStayPressed(boolean stayPressed) {
         mStayPressed = stayPressed;
-        if (!stayPressed) {
-            HolographicOutlineHelper.getInstance(getContext()).recycleShadowBitmap(mPressedBackground);
-            mPressedBackground = null;
-        } else {
-            if (mPressedBackground == null) {
-                mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
-            }
-        }
-
-        // Only show the shadow effect when persistent pressed state is set.
-        ViewParent parent = getParent();
-        if (parent != null && parent.getParent() instanceof BubbleTextShadowHandler) {
-            ((BubbleTextShadowHandler) parent.getParent()).setPressedIcon(
-                    this, mPressedBackground);
-        }
-
         refreshDrawableState();
     }
 
@@ -355,26 +316,12 @@
     }
 
     @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (super.onKeyDown(keyCode, event)) {
-            // Pre-create shadow so show immediately on click.
-            if (mPressedBackground == null) {
-                mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
-            }
-            return true;
-        }
-        return false;
-    }
-
-    @Override
     public boolean onKeyUp(int keyCode, KeyEvent event) {
         // Unlike touch events, keypress event propagate pressed state change immediately,
         // without waiting for onClickHandler to execute. Disable pressed state changes here
         // to avoid flickering.
         mIgnorePressedStateChange = true;
         boolean result = super.onKeyUp(keyCode, event);
-
-        mPressedBackground = null;
         mIgnorePressedStateChange = false;
         refreshDrawableState();
         return result;
@@ -662,11 +609,4 @@
     public int getIconSize() {
         return mIconSize;
     }
-
-    /**
-     * Interface to be implemented by the grand parent to allow click shadow effect.
-     */
-    public interface BubbleTextShadowHandler {
-        void setPressedIcon(BubbleTextView icon, Bitmap background);
-    }
 }
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 5e4f670..734aec3 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -21,6 +21,7 @@
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -45,7 +46,6 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 
-import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
 import com.android.launcher3.accessibility.FolderAccessibilityHelper;
@@ -70,7 +70,7 @@
 import java.util.Comparator;
 import java.util.Stack;
 
-public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
+public class CellLayout extends ViewGroup {
     public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
     public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
 
@@ -128,8 +128,6 @@
     private int mDragOutlineCurrent = 0;
     private final Paint mDragOutlinePaint = new Paint();
 
-    private final ClickShadowView mTouchFeedbackView;
-
     @Thunk final ArrayMap<LayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
     @Thunk final ArrayMap<View, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
 
@@ -285,9 +283,6 @@
         mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
 
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
-
-        mTouchFeedbackView = new ClickShadowView(context);
-        addView(mTouchFeedbackView);
         addView(mShortcutsAndWidgets);
     }
 
@@ -384,11 +379,6 @@
         return mDropPending;
     }
 
-    @Override
-    public void setPressedIcon(BubbleTextView icon, Bitmap background) {
-        mTouchFeedbackView.setPressedIcon(icon, background);
-    }
-
     void setIsDragOverlapping(boolean isDragOverlapping) {
         if (mIsDragOverlapping != isDragOverlapping) {
             mIsDragOverlapping = isDragOverlapping;
@@ -785,13 +775,6 @@
             throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
         }
 
-        // Make the feedback view large enough to hold the blur bitmap.
-        mTouchFeedbackView.measure(
-                MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
-                        MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
-                        MeasureSpec.EXACTLY));
-
         mShortcutsAndWidgets.measure(
                 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
                 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
@@ -815,11 +798,7 @@
         int top = getPaddingTop();
         int bottom = b - t - getPaddingBottom();
 
-        mTouchFeedbackView.layout(left, top,
-                left + mTouchFeedbackView.getMeasuredWidth(),
-                top + mTouchFeedbackView.getMeasuredHeight());
         mShortcutsAndWidgets.layout(left, top, right, bottom);
-
         // Expand the background drawing bounds by the padding baked into the background drawable
         mBackground.getPadding(mTempRect);
         mBackground.setBounds(
@@ -1012,6 +991,7 @@
         }
     }
 
+    @SuppressLint("StringFormatMatches")
     public String getItemMoveDescription(int cellX, int cellY) {
         if (mContainerType == HOTSEAT) {
             return getContext().getString(R.string.move_to_hotseat_position,
diff --git a/src/com/android/launcher3/ClickShadowView.java b/src/com/android/launcher3/ClickShadowView.java
deleted file mode 100644
index 5391b4d..0000000
--- a/src/com/android/launcher3/ClickShadowView.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import static com.android.launcher3.FastBitmapDrawable.CLICK_FEEDBACK_DURATION;
-import static com.android.launcher3.FastBitmapDrawable.CLICK_FEEDBACK_INTERPOLATOR;
-import static com.android.launcher3.LauncherAnimUtils.ELEVATION;
-import static com.android.launcher3.graphics.HolographicOutlineHelper.ADAPTIVE_ICON_SHADOW_BITMAP;
-
-import android.animation.ObjectAnimator;
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Outline;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.util.Property;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-
-public class ClickShadowView extends View {
-
-    private static final int SHADOW_SIZE_FACTOR = 3;
-    private static final int SHADOW_LOW_ALPHA = 30;
-    private static final int SHADOW_HIGH_ALPHA = 60;
-
-    private static float sAdaptiveIconScaleFactor = 1f;
-
-    private final Paint mPaint;
-
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private final float mShadowOffset;
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private final float mShadowPadding;
-
-    private Bitmap mBitmap;
-    private ObjectAnimator mAnim;
-
-    private Drawable mAdaptiveIcon;
-    private ViewOutlineProvider mOutlineProvider;
-
-    public ClickShadowView(Context context) {
-        super(context);
-        mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
-        mPaint.setColor(Color.BLACK);
-
-        mShadowPadding = getResources().getDimension(R.dimen.blur_size_click_shadow);
-        mShadowOffset = getResources().getDimension(R.dimen.click_shadow_high_shift);
-    }
-
-    public static void setAdaptiveIconScaleFactor(float factor) {
-        sAdaptiveIconScaleFactor = factor;
-    }
-
-    /**
-     * @return extra space required by the view to show the shadow.
-     */
-    public int getExtraSize() {
-        return (int) (SHADOW_SIZE_FACTOR * mShadowPadding);
-    }
-
-    public void setPressedIcon(BubbleTextView icon, Bitmap background) {
-        if (icon == null) {
-            setBitmap(null);
-            cancelAnim();
-            return;
-        }
-        if (background == null) {
-            if (mBitmap == ADAPTIVE_ICON_SHADOW_BITMAP) {
-                // clear animation shadow
-            }
-            setBitmap(null);
-            cancelAnim();
-            icon.setOutlineProvider(null);
-        } else if (setBitmap(background)) {
-            if (mBitmap == ADAPTIVE_ICON_SHADOW_BITMAP) {
-                setupAdaptiveShadow(icon);
-                cancelAnim();
-                startAnim(icon, ELEVATION,
-                        getResources().getDimension(R.dimen.click_shadow_elevation));
-            } else {
-                alignWithIconView(icon);
-                startAnim(this, ALPHA, 1);
-            }
-        }
-    }
-
-    @TargetApi(Build.VERSION_CODES.O)
-    private void setupAdaptiveShadow(final BubbleTextView view) {
-        if (mAdaptiveIcon == null) {
-            mAdaptiveIcon = new AdaptiveIconDrawable(null, null);
-            mOutlineProvider = new ViewOutlineProvider() {
-                @Override
-                public void getOutline(View view, Outline outline) {
-                    mAdaptiveIcon.getOutline(outline);
-                }
-            };
-        }
-
-        int iconWidth = view.getRight() - view.getLeft();
-        int iconHSpace = iconWidth - view.getCompoundPaddingRight() - view.getCompoundPaddingLeft();
-        int drawableWidth = view.getIcon().getBounds().width();
-
-        Rect bounds = new Rect();
-        bounds.left = view.getCompoundPaddingLeft() + (iconHSpace - drawableWidth) / 2;
-        bounds.right = bounds.left + drawableWidth;
-        bounds.top = view.getPaddingTop();
-        bounds.bottom = bounds.top + view.getIcon().getBounds().height();
-        Utilities.scaleRectAboutCenter(bounds, sAdaptiveIconScaleFactor);
-
-        mAdaptiveIcon.setBounds(bounds);
-        view.setOutlineProvider(mOutlineProvider);
-    }
-
-    /**
-     * Applies the new bitmap.
-     * @return true if the view was invalidated.
-     */
-    private boolean setBitmap(Bitmap b) {
-        if (b != mBitmap){
-            mBitmap = b;
-            invalidate();
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        if (mBitmap != null) {
-            mPaint.setAlpha(SHADOW_LOW_ALPHA);
-            canvas.drawBitmap(mBitmap, 0, 0, mPaint);
-            mPaint.setAlpha(SHADOW_HIGH_ALPHA);
-            canvas.drawBitmap(mBitmap, 0, mShadowOffset, mPaint);
-        }
-    }
-
-    private void cancelAnim() {
-        if (mAnim != null) {
-            mAnim.cancel();
-            mAnim.setCurrentPlayTime(0);
-            mAnim = null;
-        }
-    }
-
-    private void startAnim(View target, Property<View, Float> property, float endValue) {
-        cancelAnim();
-        property.set(target, 0f);
-        mAnim = ObjectAnimator.ofFloat(target, property, endValue);
-        mAnim.setDuration(CLICK_FEEDBACK_DURATION)
-                .setInterpolator(CLICK_FEEDBACK_INTERPOLATOR);
-        mAnim.start();
-    }
-
-    /**
-     * Aligns the shadow with {@param view}
-     * Note: {@param view} must be a descendant of my parent.
-     */
-    private void alignWithIconView(BubbleTextView view) {
-        int[] coords = new int[] {0, 0};
-        Utilities.getDescendantCoordRelativeToAncestor(
-                (ViewGroup) view.getParent(), (View) getParent(), coords, false);
-
-        float leftShift = view.getLeft() + coords[0] - getLeft();
-        float topShift = view.getTop() + coords[1] - getTop();
-        int iconWidth = view.getRight() - view.getLeft();
-        int iconHeight = view.getBottom() - view.getTop();
-        int iconHSpace = iconWidth - view.getCompoundPaddingRight() - view.getCompoundPaddingLeft();
-        float drawableWidth = view.getIcon().getBounds().width();
-
-        // Set the bounds to clip against
-        int clipLeft = (int) Math.max(0, coords[0] - leftShift - mShadowPadding);
-        int clipTop = (int) Math.max(0, coords[1] - topShift - mShadowPadding) ;
-        setClipBounds(new Rect(clipLeft, clipTop, clipLeft + iconWidth, clipTop + iconHeight));
-
-        setTranslationX(leftShift
-                + view.getCompoundPaddingLeft() * view.getScaleX()
-                + (iconHSpace - drawableWidth) * view.getScaleX() / 2  /* drawable gap */
-                + iconWidth * (1 - view.getScaleX()) / 2  /* gap due to scale */
-                - mShadowPadding  /* extra shadow size */
-                );
-        setTranslationY(topShift
-                + view.getPaddingTop() * view.getScaleY()  /* drawable gap */
-                + view.getHeight() * (1 - view.getScaleY()) / 2  /* gap due to scale */
-                - mShadowPadding  /* extra shadow size */
-                );
-    }
-}
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index c4ec8c9..5354edf 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -16,8 +16,9 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+
 import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -28,9 +29,8 @@
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
 import android.util.Property;
 import android.util.SparseArray;
 
@@ -38,14 +38,12 @@
 
 public class FastBitmapDrawable extends Drawable {
 
-    private static final float PRESSED_BRIGHTNESS = 100f / 255f;
+    private static final float PRESSED_SCALE = 1.1f;
+
     private static final float DISABLED_DESATURATION = 1f;
     private static final float DISABLED_BRIGHTNESS = 0.5f;
 
-    public static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = (input) ->
-            (input < 0.05f) ? (input / 0.05f) : ((input < 0.3f) ? 1 : (1 - input) / 0.7f);
-
-    public static final int CLICK_FEEDBACK_DURATION = 2000;
+    public static final int CLICK_FEEDBACK_DURATION = 200;
 
     // Since we don't need 256^2 values for combinations of both the brightness and saturation, we
     // reduce the value space to a smaller value V, which reduces the number of cached
@@ -66,18 +64,23 @@
     private boolean mIsPressed;
     private boolean mIsDisabled;
 
-    private static final Property<FastBitmapDrawable, Float> BRIGHTNESS
-            = new Property<FastBitmapDrawable, Float>(Float.TYPE, "brightness") {
+    // Animator and properties for the fast bitmap drawable's scale
+    private static final Property<FastBitmapDrawable, Float> SCALE
+            = new Property<FastBitmapDrawable, Float>(Float.TYPE, "scale") {
         @Override
         public Float get(FastBitmapDrawable fastBitmapDrawable) {
-            return fastBitmapDrawable.getBrightness();
+            return fastBitmapDrawable.mScale;
         }
 
         @Override
         public void set(FastBitmapDrawable fastBitmapDrawable, Float value) {
-            fastBitmapDrawable.setBrightness(value);
+            fastBitmapDrawable.mScale = value;
+            fastBitmapDrawable.invalidateSelf();
         }
     };
+    private ObjectAnimator mScaleAnimation;
+    private float mScale = 1;
+
 
     // The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
     // as a result, can be used to compose the key for the cached ColorMatrixColorFilters
@@ -86,9 +89,6 @@
     private int mAlpha = 255;
     private int mPrevUpdateKey = Integer.MAX_VALUE;
 
-    // Animators for the fast bitmap drawable's brightness
-    private ObjectAnimator mBrightnessAnimator;
-
     public FastBitmapDrawable(Bitmap b) {
         this(b, Color.TRANSPARENT);
     }
@@ -108,8 +108,20 @@
     }
 
     @Override
-    public void draw(Canvas canvas) {
-        canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
+    public final void draw(Canvas canvas) {
+        if (mScaleAnimation != null) {
+            int count = canvas.save();
+            Rect bounds = getBounds();
+            canvas.scale(mScale, mScale, bounds.exactCenterX(), bounds.exactCenterY());
+            drawInternal(canvas, bounds);
+            canvas.restoreToCount(count);
+        } else {
+            drawInternal(canvas, getBounds());
+        }
+    }
+
+    protected void drawInternal(Canvas canvas, Rect bounds) {
+        canvas.drawBitmap(mBitmap, null, bounds, mPaint);
     }
 
     @Override
@@ -138,6 +150,10 @@
         return mAlpha;
     }
 
+    public float getAnimatedScale() {
+        return mScaleAnimation == null ? 1 : mScale;
+    }
+
     @Override
     public int getIntrinsicWidth() {
         return mBitmap.getWidth();
@@ -184,19 +200,20 @@
         if (mIsPressed != isPressed) {
             mIsPressed = isPressed;
 
-            if (mBrightnessAnimator != null) {
-                mBrightnessAnimator.cancel();
+            if (mScaleAnimation != null) {
+                mScaleAnimation.cancel();
+                mScaleAnimation = null;
             }
 
             if (mIsPressed) {
                 // Animate when going to pressed state
-                mBrightnessAnimator = ObjectAnimator.ofFloat(
-                        this, BRIGHTNESS, getExpectedBrightness());
-                mBrightnessAnimator.setDuration(CLICK_FEEDBACK_DURATION);
-                mBrightnessAnimator.setInterpolator(CLICK_FEEDBACK_INTERPOLATOR);
-                mBrightnessAnimator.start();
+                mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, PRESSED_SCALE);
+                mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
+                mScaleAnimation.setInterpolator(ACCEL);
+                mScaleAnimation.start();
             } else {
-                setBrightness(getExpectedBrightness());
+                mScale = 1f;
+                invalidateSelf();
             }
             return true;
         }
@@ -205,12 +222,7 @@
 
     private void invalidateDesaturationAndBrightness() {
         setDesaturation(mIsDisabled ? DISABLED_DESATURATION : 0);
-        setBrightness(getExpectedBrightness());
-    }
-
-    private float getExpectedBrightness() {
-        return mIsDisabled ? DISABLED_BRIGHTNESS :
-                (mIsPressed ? PRESSED_BRIGHTNESS : 0);
+        setBrightness(mIsDisabled ? DISABLED_BRIGHTNESS : 0);
     }
 
     public void setIsDisabled(boolean isDisabled) {
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index d0581a2..f4ae62a 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -45,12 +45,10 @@
 
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.BitmapInfo;
-import com.android.launcher3.graphics.ColorExtractor;
+import com.android.launcher3.graphics.BitmapRenderer;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.Preconditions;
@@ -126,7 +124,7 @@
         // automatically be loaded as ALPHA_8888.
         mLowResOptions.inPreferredConfig = Bitmap.Config.RGB_565;
 
-        if (UiFactory.USE_HARDWARE_BITMAP) {
+        if (BitmapRenderer.USE_HARDWARE_BITMAP) {
             mHighResOptions = new BitmapFactory.Options();
             mHighResOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
         } else {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 38235f4..375deb7 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -18,13 +18,7 @@
 
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_QUIET_USER;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
+
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
@@ -40,7 +34,6 @@
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
-import android.app.AlertDialog;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
 import android.content.ActivityNotFoundException;
@@ -48,12 +41,10 @@
 import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.content.ContextWrapper;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.database.sqlite.SQLiteDatabase;
@@ -82,6 +73,7 @@
 import android.view.Menu;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
@@ -101,10 +93,8 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.dynamicui.WallpaperColorInfo;
-import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.keyboard.CustomActionsPopup;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
@@ -116,6 +106,8 @@
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.states.InternalStateHandler;
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -156,7 +148,7 @@
  * Default launcher application.
  */
 public class Launcher extends BaseActivity
-        implements LauncherExterns, View.OnClickListener, OnLongClickListener,
+        implements LauncherExterns, OnClickListener, OnLongClickListener,
                    LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener,
                    WallpaperColorInfo.OnThemeChangeListener {
     public static final String TAG = "Launcher";
@@ -171,8 +163,8 @@
     private static final int REQUEST_PICK_WALLPAPER = 10;
 
     private static final int REQUEST_BIND_APPWIDGET = 11;
-    private static final int REQUEST_BIND_PENDING_APPWIDGET = 12;
-    private static final int REQUEST_RECONFIGURE_APPWIDGET = 13;
+    public static final int REQUEST_BIND_PENDING_APPWIDGET = 12;
+    public static final int REQUEST_RECONFIGURE_APPWIDGET = 13;
 
     private static final int REQUEST_PERMISSION_CALL_PHONE = 14;
 
@@ -272,10 +264,9 @@
     private final PointF mLastDispatchTouchEvent = new PointF();
 
     public ViewGroupFocusHelper mFocusHandler;
-    private boolean mRotationEnabled = false;
     private boolean mAppLaunchSuccess;
 
-    private RotationPrefChangeHandler mRotationPrefChangeHandler;
+    private RotationHelper mRotationHelper;
     private ActionMode mCurrentActionMode;
 
     @Override
@@ -327,20 +318,10 @@
         setupViews();
         mPopupDataProvider = new PopupDataProvider(this);
 
-        mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
-        // In case we are on a device with locked rotation, we should look at preferences to check
-        // if the user has specifically allowed rotation.
-        if (!mRotationEnabled) {
-            mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
-            mRotationPrefChangeHandler = new RotationPrefChangeHandler();
-            mSharedPrefs.registerOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
-        }
+        mRotationHelper = new RotationHelper(this);
 
         boolean internalStateHandled = InternalStateHandler.handleCreate(this, getIntent());
         if (internalStateHandled) {
-            // Temporarily enable the rotation
-            mRotationEnabled = true;
-
             if (savedInstanceState != null) {
                 // InternalStateHandler has already set the appropriate state.
                 // We dont need to do anything.
@@ -372,7 +353,6 @@
         // For handling default keys
         setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
 
-        updateRequestedOrientation();
         setContentView(mLauncherView);
         getRootView().dispatchInsets();
 
@@ -391,17 +371,11 @@
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onCreate(savedInstanceState);
         }
+        mRotationHelper.initialize();
 
         TraceHelper.endSection("Launcher-onCreate");
     }
 
-    public void updateRequestedOrientation() {
-        // On large interfaces, or on devices that a user has specifically enabled screen rotation,
-        // we want the screen to auto-rotate based on the current orientation
-        setRequestedOrientation(mRotationEnabled
-                ? SCREEN_ORIENTATION_UNSPECIFIED : SCREEN_ORIENTATION_NOSENSOR);
-    }
-
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         int diff = newConfig.diff(mOldConfig);
@@ -419,17 +393,22 @@
             getStateManager().reapplyState();
 
             // TODO: We can probably avoid rebind when only screen size changed.
-            int currentPage = mWorkspace.getNextPage();
-            if (mModel.startLoader(currentPage)) {
-                mWorkspace.setCurrentPage(currentPage);
-                setWorkspaceLoading(true);
-            }
+            rebindModel();
         }
 
         mOldConfig.setTo(newConfig);
         super.onConfigurationChanged(newConfig);
     }
 
+    @Override
+    public void rebindModel() {
+        int currentPage = mWorkspace.getNextPage();
+        if (mModel.startLoader(currentPage)) {
+            mWorkspace.setCurrentPage(currentPage);
+            setWorkspaceLoading(true);
+        }
+    }
+
     private void initDeviceProfile(InvariantDeviceProfile idp) {
         // Load configuration-specific DeviceProfile
         mDeviceProfile = idp.getDeviceProfile(this);
@@ -439,7 +418,11 @@
             display.getSize(mwSize);
             mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
         }
-        mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout());
+        mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout(), true);
+    }
+
+    public RotationHelper getRotationHelper() {
+        return mRotationHelper;
     }
 
     @Override
@@ -1027,7 +1010,7 @@
         BubbleTextView favorite = (BubbleTextView) LayoutInflater.from(parent.getContext())
                 .inflate(R.layout.app_icon, parent, false);
         favorite.applyFromShortcutInfo(info);
-        favorite.setOnClickListener(this);
+        favorite.setOnClickListener(ItemClickHandler.INSTANCE);
         favorite.setOnFocusChangeListener(mFocusHandler);
         return favorite;
     }
@@ -1381,10 +1364,7 @@
             mModel.stopLoader();
             LauncherAppState.getInstance(this).setLauncher(null);
         }
-
-        if (mRotationPrefChangeHandler != null) {
-            mSharedPrefs.unregisterOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
-        }
+        mRotationHelper.destroy();
 
         try {
             mAppWidgetHost.stopListening();
@@ -1683,6 +1663,7 @@
      *
      * @param v The view representing the clicked shortcut.
      */
+    @Override
     public void onClick(View v) {
         // Make sure that rogue clicks don't get through while allapps is launching, or after the
         // view has detached (it's possible for this to happen if the view is removed mid touch).
@@ -1715,21 +1696,6 @@
             }
             return;
         }
-
-        Object tag = v.getTag();
-        if (tag instanceof ShortcutInfo) {
-            onClickAppShortcut(v);
-        } else if (tag instanceof FolderInfo) {
-            if (v instanceof FolderIcon) {
-                onClickFolderIcon(v);
-            }
-        } else if (tag instanceof AppInfo) {
-            startAppShortcutOrInfoActivity(v);
-        } else if (tag instanceof LauncherAppWidgetInfo) {
-            if (v instanceof PendingAppWidgetHostView) {
-                onClickPendingWidget((PendingAppWidgetHostView) v);
-            }
-        }
     }
 
     @SuppressLint("ClickableViewAccessibility")
@@ -1738,173 +1704,6 @@
     }
 
     /**
-     * Event handler for the app widget view which has not fully restored.
-     */
-    public void onClickPendingWidget(final PendingAppWidgetHostView v) {
-        if (mIsSafeModeEnabled) {
-            Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
-            return;
-        }
-
-        final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
-        if (v.isReadyForClickSetup()) {
-            LauncherAppWidgetProviderInfo appWidgetInfo =
-                    mAppWidgetManager.findProvider(info.providerName, info.user);
-            if (appWidgetInfo == null) {
-                return;
-            }
-            WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo);
-
-            if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
-                if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
-                    // This should not happen, as we make sure that an Id is allocated during bind.
-                    return;
-                }
-                addFlowHandler.startBindFlow(this, info.appWidgetId, info,
-                        REQUEST_BIND_PENDING_APPWIDGET);
-            } else {
-                addFlowHandler.startConfigActivity(this, info, REQUEST_RECONFIGURE_APPWIDGET);
-            }
-        } else {
-            final String packageName = info.providerName.getPackageName();
-            onClickPendingAppItem(v, packageName, info.installProgress >= 0);
-        }
-    }
-
-    private void onClickPendingAppItem(final View v, final String packageName,
-            boolean downloadStarted) {
-        if (downloadStarted) {
-            // If the download has started, simply direct to the market app.
-            startMarketIntentForPackage(v, packageName);
-            return;
-        }
-        new AlertDialog.Builder(this)
-            .setTitle(R.string.abandoned_promises_title)
-            .setMessage(R.string.abandoned_promise_explanation)
-            .setPositiveButton(R.string.abandoned_search, new DialogInterface.OnClickListener() {
-                @Override
-                public void onClick(DialogInterface dialogInterface, int i) {
-                    startMarketIntentForPackage(v, packageName);
-                }
-            })
-            .setNeutralButton(R.string.abandoned_clean_this,
-                new DialogInterface.OnClickListener() {
-                    public void onClick(DialogInterface dialog, int id) {
-                        final UserHandle user = Process.myUserHandle();
-                        mWorkspace.removeAbandonedPromise(packageName, user);
-                    }
-                })
-            .create().show();
-    }
-
-    private void startMarketIntentForPackage(View v, String packageName) {
-        ItemInfo item = (ItemInfo) v.getTag();
-        Intent intent = new PackageManagerHelper(v.getContext()).getMarketIntent(packageName);
-        startActivitySafely(v, intent, item);
-    }
-
-    /**
-     * Event handler for an app shortcut click.
-     *
-     * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
-     */
-    protected void onClickAppShortcut(final View v) {
-        if (LOGD) Log.d(TAG, "onClickAppShortcut");
-        Object tag = v.getTag();
-        if (!(tag instanceof ShortcutInfo)) {
-            throw new IllegalArgumentException("Input must be a Shortcut");
-        }
-
-        // Open shortcut
-        final ShortcutInfo shortcut = (ShortcutInfo) tag;
-
-        if (shortcut.isDisabled()) {
-            final int disabledFlags = shortcut.runtimeStatusFlags & ShortcutInfo.FLAG_DISABLED_MASK;
-            if ((disabledFlags &
-                    ~FLAG_DISABLED_SUSPENDED &
-                    ~FLAG_DISABLED_QUIET_USER) == 0) {
-                // If the app is only disabled because of the above flags, launch activity anyway.
-                // Framework will tell the user why the app is suspended.
-            } else {
-                if (!TextUtils.isEmpty(shortcut.disabledMessage)) {
-                    // Use a message specific to this shortcut, if it has one.
-                    Toast.makeText(this, shortcut.disabledMessage, Toast.LENGTH_SHORT).show();
-                    return;
-                }
-                // Otherwise just use a generic error message.
-                int error = R.string.activity_not_available;
-                if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) {
-                    error = R.string.safemode_shortcut_error;
-                } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 ||
-                        (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) {
-                    error = R.string.shortcut_not_available;
-                }
-                Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
-                return;
-            }
-        }
-
-        // Check for abandoned promise
-        if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()) {
-            String packageName = shortcut.intent.getComponent() != null ?
-                    shortcut.intent.getComponent().getPackageName() : shortcut.intent.getPackage();
-            if (!TextUtils.isEmpty(packageName)) {
-                onClickPendingAppItem(v, packageName,
-                        shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE));
-                return;
-            }
-        }
-
-        // Start activities
-        startAppShortcutOrInfoActivity(v);
-    }
-
-    private void startAppShortcutOrInfoActivity(View v) {
-        ItemInfo item = (ItemInfo) v.getTag();
-        Intent intent;
-        if (item instanceof PromiseAppInfo) {
-            PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
-            intent = promiseAppInfo.getMarketIntent(this);
-        } else {
-            intent = item.getIntent();
-        }
-        if (intent == null) {
-            throw new IllegalArgumentException("Input must have a valid intent");
-        }
-        if (item instanceof ShortcutInfo) {
-            ShortcutInfo si = (ShortcutInfo) item;
-            if (si.hasStatusFlag(ShortcutInfo.FLAG_SUPPORTS_WEB_UI)
-                    && intent.getAction() == Intent.ACTION_VIEW) {
-                // make a copy of the intent that has the package set to null
-                // we do this because the platform sometimes disables instant
-                // apps temporarily (triggered by the user) and fallbacks to the
-                // web ui. This only works though if the package isn't set
-                intent = new Intent(intent);
-                intent.setPackage(null);
-            }
-        }
-        startActivitySafely(v, intent, item);
-    }
-
-    /**
-     * Event handler for a folder icon click.
-     *
-     * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
-     */
-    protected void onClickFolderIcon(View v) {
-        if (LOGD) Log.d(TAG, "onClickFolder");
-        if (!(v instanceof FolderIcon)){
-            throw new IllegalArgumentException("Input must be a FolderIcon");
-        }
-
-        Folder folder = ((FolderIcon) v).getFolder();
-        if (!folder.isOpen() && !folder.isDestroyed()) {
-            // Open the requested folder
-            folder.animateOpen();
-        }
-    }
-
-    /**
      * Event handler for the wallpaper picker button that appears after a long press
      * on the home screen.
      */
@@ -2094,45 +1893,25 @@
             }
         }
 
-        CellLayout.CellInfo longClickCellInfo = null;
-        View itemUnderLongClick = null;
-        if (v.getTag() instanceof ItemInfo) {
-            ItemInfo info = (ItemInfo) v.getTag();
-            longClickCellInfo = new CellLayout.CellInfo(v, info);
-            itemUnderLongClick = longClickCellInfo.cell;
-            mPendingRequestArgs = null;
-        }
-
         // The hotseat touch handling does not go through Workspace, and we always allow long press
         // on hotseat items.
         if (!mDragController.isDragging()) {
-            if (itemUnderLongClick == null) {
-                // User long pressed on empty space
-                if (mWorkspace.isPageRearrangeEnabled()) {
-                    mWorkspace.startReordering(v);
-                    getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
-                            Action.Direction.NONE, ContainerType.OVERVIEW);
-                } else {
-                    if (ignoreLongPressToOverview) {
-                        return false;
-                    }
-                    getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
-                            Action.Direction.NONE, ContainerType.WORKSPACE,
-                            mWorkspace.getCurrentPage());
-                    UiFactory.onWorkspaceLongPress(this, mLastDispatchTouchEvent);
-                }
-                mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
-                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+            // User long pressed on empty space
+            if (mWorkspace.isPageRearrangeEnabled()) {
+                mWorkspace.startReordering(v);
+                getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
+                        Action.Direction.NONE, ContainerType.OVERVIEW);
             } else {
-                final boolean isAllAppsButton =
-                        !FeatureFlags.NO_ALL_APPS_ICON && isHotseatLayout(v) &&
-                                mDeviceProfile.inv.isAllAppsButtonRank(mHotseat.getOrderInHotseat(
-                                        longClickCellInfo.cellX, longClickCellInfo.cellY));
-                if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
-                    // User long pressed on an item
-                    mWorkspace.startDrag(longClickCellInfo, new DragOptions());
+                if (ignoreLongPressToOverview) {
+                    return false;
                 }
+                getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
+                        Action.Direction.NONE, ContainerType.WORKSPACE,
+                        mWorkspace.getCurrentPage());
+                UiFactory.onWorkspaceLongPress(this, mLastDispatchTouchEvent);
             }
+            mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
         }
         return true;
     }
@@ -2727,10 +2506,6 @@
         mModel.refreshAndBindWidgetsAndShortcuts(packageUser);
     }
 
-    public boolean isRotationEnabled () {
-        return mRotationEnabled;
-    }
-
     /**
      * $ adb shell dumpsys activity com.android.launcher3.Launcher [--all]
      */
@@ -2868,18 +2643,6 @@
         return ((Launcher) ((ContextWrapper) context).getBaseContext());
     }
 
-    private class RotationPrefChangeHandler implements OnSharedPreferenceChangeListener {
-
-        @Override
-        public void onSharedPreferenceChanged(
-                SharedPreferences sharedPreferences, String key) {
-            if (Utilities.ALLOW_ROTATION_PREFERENCE_KEY.equals(key)) {
-                // Recreate the activity so that it initializes the rotation preference again.
-                recreate();
-            }
-        }
-    }
-
     @Override
     public void onActionModeStarted(ActionMode mode) {
         super.onActionModeStarted(mode);
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 6646b78..04a32f7 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -135,6 +135,8 @@
     };
 
     public interface Callbacks {
+        public void rebindModel();
+
         public int getCurrentWorkspaceScreen();
         public void clearPendingBinds();
         public void startBinding();
@@ -196,8 +198,9 @@
         enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
     }
 
-    public ModelWriter getWriter(boolean hasVerticalHotseat) {
-        return new ModelWriter(mApp.getContext(), sBgDataModel, hasVerticalHotseat);
+    public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges) {
+        return new ModelWriter(mApp.getContext(), this, sBgDataModel,
+                hasVerticalHotseat, verifyChanges);
     }
 
     static void checkItemInfoLocked(
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 54df1da..4e6bcdc 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -20,12 +20,13 @@
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
 
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
 
 import android.view.View;
 import android.view.animation.Interpolator;
 
-import com.android.launcher3.uioverrides.AllAppsState;
 import com.android.launcher3.states.SpringLoadedState;
+import com.android.launcher3.uioverrides.AllAppsState;
 import com.android.launcher3.uioverrides.FastOverviewState;
 import com.android.launcher3.uioverrides.OverviewState;
 import com.android.launcher3.uioverrides.UiFactory;
@@ -211,6 +212,8 @@
     public void onStateTransitionEnd(Launcher launcher) {
         if (this == NORMAL) {
             UiFactory.resetOverview(launcher);
+            // Clear any rotation locks when going to normal state
+            launcher.getRotationHelper().setCurrentStateRequest(REQUEST_NONE);
         }
     }
 
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
index d40ac8f..6a4e93b 100644
--- a/src/com/android/launcher3/SettingsActivity.java
+++ b/src/com/android/launcher3/SettingsActivity.java
@@ -65,7 +65,6 @@
      */
     public static class LauncherSettingsFragment extends PreferenceFragment {
 
-        private SystemDisplayRotationLockObserver mRotationLockObserver;
         private IconBadgingObserver mIconBadgingObserver;
 
         @Override
@@ -76,22 +75,6 @@
 
             ContentResolver resolver = getActivity().getContentResolver();
 
-            // Setup allow rotation preference
-            Preference rotationPref = findPreference(Utilities.ALLOW_ROTATION_PREFERENCE_KEY);
-            if (getResources().getBoolean(R.bool.allow_rotation)) {
-                // Launcher supports rotation by default. No need to show this setting.
-                getPreferenceScreen().removePreference(rotationPref);
-            } else {
-                mRotationLockObserver = new SystemDisplayRotationLockObserver(rotationPref, resolver);
-
-                // Register a content observer to listen for system setting changes while
-                // this UI is active.
-                mRotationLockObserver.register(Settings.System.ACCELEROMETER_ROTATION);
-
-                // Initialize the UI once
-                rotationPref.setDefaultValue(Utilities.getAllowRotationDefaultValue(getActivity()));
-            }
-
             ButtonPreference iconBadgingPref =
                     (ButtonPreference) findPreference(ICON_BADGING_PREFERENCE_KEY);
             if (!Utilities.ATLEAST_OREO) {
@@ -119,10 +102,6 @@
 
         @Override
         public void onDestroy() {
-            if (mRotationLockObserver != null) {
-                mRotationLockObserver.unregister();
-                mRotationLockObserver = null;
-            }
             if (mIconBadgingObserver != null) {
                 mIconBadgingObserver.unregister();
                 mIconBadgingObserver = null;
@@ -132,28 +111,6 @@
     }
 
     /**
-     * Content observer which listens for system auto-rotate setting changes, and enables/disables
-     * the launcher rotation setting accordingly.
-     */
-    private static class SystemDisplayRotationLockObserver extends SettingsObserver.System {
-
-        private final Preference mRotationPref;
-
-        public SystemDisplayRotationLockObserver(
-                Preference rotationPref, ContentResolver resolver) {
-            super(resolver);
-            mRotationPref = rotationPref;
-        }
-
-        @Override
-        public void onSettingChanged(boolean enabled) {
-            mRotationPref.setEnabled(enabled);
-            mRotationPref.setSummary(enabled
-                    ? R.string.allow_rotation_desc : R.string.allow_rotation_blocked_desc);
-        }
-    }
-
-    /**
      * Content observer which listens for system badging setting changes,
      * and updates the launcher badging setting subtext accordingly.
      */
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index ec608ca..8588c7a 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -79,7 +79,7 @@
      * A message to display when the user tries to start a disabled shortcut.
      * This is currently only used for deep shortcuts.
      */
-    CharSequence disabledMessage;
+    public CharSequence disabledMessage;
 
     public int status;
 
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 31dab97..cabccbf 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -125,29 +125,10 @@
             CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
             TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
 
-    public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
-
     public static boolean isPropertyEnabled(String propertyName) {
         return Log.isLoggable(propertyName, Log.VERBOSE);
     }
 
-    public static boolean isAllowRotationPrefEnabled(Context context) {
-        return getPrefs(context).getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
-                getAllowRotationDefaultValue(context));
-    }
-
-    public static boolean getAllowRotationDefaultValue(Context context) {
-        if (ATLEAST_NOUGAT) {
-            // If the device was scaled, used the original dimensions to determine if rotation
-            // is allowed of not.
-            Resources res = context.getResources();
-            int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp
-                    * res.getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEVICE_STABLE;
-            return originalSmallestWidth >= 600;
-        }
-        return false;
-    }
-
     /**
      * Given a coordinate relative to the descendant, find the coordinate in a parent view's
      * coordinates.
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 7c42b48..f329f5e 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -81,6 +81,7 @@
 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -935,10 +936,9 @@
             Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
         }
 
-        if (!(child instanceof Folder)) {
-            child.setHapticFeedbackEnabled(false);
-            child.setOnLongClickListener(mLongClickListener);
-        }
+        child.setHapticFeedbackEnabled(false);
+        child.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
+
         if (child instanceof DropTarget) {
             mDragController.addDropTarget((DropTarget) child);
         }
@@ -1635,7 +1635,6 @@
                 new DragPreviewProvider(child), options);
     }
 
-
     public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
             DragPreviewProvider previewProvider, DragOptions dragOptions) {
         child.clearFocus();
@@ -2970,8 +2969,9 @@
                         + "Workspace#onDropCompleted. Please file a bug. ");
             }
         }
-        if (d.cancelled && mDragInfo != null && mDragInfo.cell != null) {
-            mDragInfo.cell.setVisibility(VISIBLE);
+        View cell = getHomescreenIconByItemId(d.originalDragInfo.id);
+        if (d.cancelled && cell != null) {
+            cell.setVisibility(VISIBLE);
         }
         mDragInfo = null;
     }
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 77ac9de..f6d0248 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -137,6 +137,8 @@
 
         propertySetter.setViewAlpha(mLauncher.getHotseat(), state.getHoseatAlpha(mLauncher),
                 pageAlphaProvider.interpolator);
+        propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
+                state.getHoseatAlpha(mLauncher), pageAlphaProvider.interpolator);
 
         // Set scrim
         propertySetter.setFloat(ViewScrim.get(mWorkspace), ViewScrim.PROGRESS,
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 2cd8b1d..4398f6e 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -25,6 +25,7 @@
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings;
@@ -358,29 +359,14 @@
             mDragInfo.dragType = DragType.WIDGET;
         }
 
-        CellLayout.CellInfo cellInfo = new CellLayout.CellInfo(item, info);
-
         Rect pos = new Rect();
         mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
         mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY());
-
-        Folder folder = Folder.getOpen(mLauncher);
-        if (folder != null) {
-            if (!folder.getItemsInReadingOrder().contains(item)) {
-                folder.close(true);
-                folder = null;
-            }
-        }
-
         mLauncher.getDragController().addDragListener(this);
 
         DragOptions options = new DragOptions();
         options.isAccessibleDrag = true;
-        if (folder != null) {
-            folder.startDrag(cellInfo.cell, options);
-        } else {
-            mLauncher.getWorkspace().startDrag(cellInfo, options);
-        }
+        ItemLongClickListener.beginDrag(item, mLauncher, info, options);
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 82bdef9..3fe5d7a 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -18,7 +18,9 @@
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 
 import android.content.Context;
-import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Process;
 import android.support.annotation.NonNull;
@@ -32,49 +34,43 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
-import android.widget.RelativeLayout;
 
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
-import com.android.launcher3.ClickShadowView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.DragSource;
-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;
 import com.android.launcher3.R;
-import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.graphics.ColorScrim;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BottomUserEducationView;
+import com.android.launcher3.views.RecyclerViewFastScroller;
+import com.android.launcher3.views.SpringRelativeLayout;
 
 /**
  * The all apps view container.
  */
-public class AllAppsContainerView extends RelativeLayout implements DragSource,
-        OnLongClickListener, Insettable, BubbleTextShadowHandler, OnDeviceProfileChangeListener {
+public class AllAppsContainerView extends SpringRelativeLayout implements DragSource,
+        Insettable, OnDeviceProfileChangeListener {
 
     private final Launcher mLauncher;
     private final AdapterHolder[] mAH;
-    private final ClickShadowView mTouchFeedbackView;
     private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle());
     private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher);
     private final AllAppsStore mAllAppsStore = new AllAppsStore();
 
+    private final Paint mNavBarScrimPaint;
+    private int mNavBarScrimHeight = 0;
+
     private SearchUiManager mSearchUiManager;
     private View mSearchContainer;
     private AllAppsPagedView mViewPager;
@@ -85,6 +81,9 @@
     private boolean mUsingTabs;
     private boolean mSearchModeWhileUsingTabs = false;
 
+    private RecyclerViewFastScroller mTouchHandler;
+    private final Point mFastScrollerOffset = new Point();
+
     public AllAppsContainerView(Context context) {
         this(context, null);
     }
@@ -100,34 +99,24 @@
         mLauncher.addOnDeviceProfileChangeListener(this);
 
         mSearchQueryBuilder = new SpannableStringBuilder();
-
         Selection.setSelection(mSearchQueryBuilder, 0);
 
-        mTouchFeedbackView = new ClickShadowView(context);
-        // Make the feedback view large enough to hold the blur bitmap.
-        int size = mLauncher.getDeviceProfile().allAppsIconSizePx
-                + mTouchFeedbackView.getExtraSize();
-        addView(mTouchFeedbackView, size, size);
-
         mAH = new AdapterHolder[2];
         mAH[AdapterHolder.MAIN] = new AdapterHolder(false /* isWork */);
         mAH[AdapterHolder.WORK] = new AdapterHolder(true /* isWork */);
 
+        mNavBarScrimPaint = new Paint();
+        mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
+
         mAllAppsStore.addUpdateListener(this::onAppsUpdated);
 
         // Attach a scrim to be drawn behind all-apps and hotseat
         new ColorScrim(this, Themes.getAttrColor(context, R.attr.allAppsScrimColor), DEACCEL_2)
                 .attach();
-    }
 
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        applyTouchDelegate();
-    }
-
-    private void applyTouchDelegate() {
-        // TODO: Reimplement once fast scroller is fixed.
+        addSpringView(R.id.all_apps_header);
+        addSpringView(R.id.apps_list_view);
+        addSpringView(R.id.all_apps_tabs_view_pager);
     }
 
     public AllAppsStore getAppsStore() {
@@ -135,12 +124,6 @@
     }
 
     @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        applyTouchDelegate();
-    }
-
-    @Override
     public void onDeviceProfileChanged(DeviceProfile dp) {
         for (AdapterHolder holder : mAH) {
             if (holder.recyclerView != null) {
@@ -165,11 +148,6 @@
         }
     }
 
-    @Override
-    public void setPressedIcon(BubbleTextView icon, Bitmap background) {
-        mTouchFeedbackView.setPressedIcon(icon, background);
-    }
-
     /**
      * Returns whether the view itself will handle the touch event or not.
      */
@@ -180,7 +158,38 @@
             return true;
         }
         AllAppsRecyclerView rv = getActiveRecyclerView();
-        return rv == null || rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
+        if (rv == null) {
+            return true;
+        }
+        if (rv.getScrollbar().getThumbOffsetY() >= 0 &&
+                mLauncher.getDragLayer().isEventOverView(rv.getScrollbar(), ev)) {
+            return false;
+        }
+        return rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            AllAppsRecyclerView rv = getActiveRecyclerView();
+            if (rv != null &&
+                    rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
+                mTouchHandler = rv.getScrollbar();
+            }
+        }
+        if (mTouchHandler != null) {
+            return mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mTouchHandler != null) {
+            mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
+            return true;
+        }
+        return false;
     }
 
     public AllAppsRecyclerView getActiveRecyclerView() {
@@ -204,7 +213,7 @@
             mHeader.reset();
         }
         // Reset the search bar and base recycler view after transitioning home
-        mSearchUiManager.reset();
+        mSearchUiManager.resetSearch();
     }
 
     @Override
@@ -238,37 +247,6 @@
     }
 
     @Override
-    public boolean onLongClick(final View v) {
-        // When we have exited all apps or are in transition, disregard long clicks
-        if (!mLauncher.isInState(LauncherState.ALL_APPS) ||
-                mLauncher.getWorkspace().isSwitchingState()) return false;
-        // Return if global dragging is not enabled or we are already dragging
-        if (!mLauncher.isDraggingEnabled()) return false;
-        if (mLauncher.getDragController().isDragging()) return false;
-
-        // Start the drag
-        final DragController dragController = mLauncher.getDragController();
-        dragController.addDragListener(new DragController.DragListener() {
-            @Override
-            public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
-                v.setVisibility(INVISIBLE);
-            }
-
-            @Override
-            public void onDragEnd() {
-                v.setVisibility(VISIBLE);
-                dragController.removeDragListener(this);
-            }
-        });
-
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        DragOptions options = new DragOptions();
-        options.intrinsicIconScaleFactor = (float) grid.allAppsIconSizePx / grid.iconSizePx;
-        mLauncher.getWorkspace().beginDragShared(v, this, options);
-        return false;
-    }
-
-    @Override
     public void onDropCompleted(View target, DragObject d, boolean success) { }
 
     @Override
@@ -291,25 +269,26 @@
         ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
         if (grid.isVerticalBarLayout()) {
             mlp.leftMargin = insets.left;
-            mlp.topMargin = insets.top;
             mlp.rightMargin = insets.right;
             setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
         } else {
-            mlp.leftMargin = mlp.rightMargin = mlp.topMargin = 0;
+            mlp.leftMargin = mlp.rightMargin = 0;
             setPadding(0, 0, 0, 0);
         }
         setLayoutParams(mlp);
 
-        View navBarBg = findViewById(R.id.nav_bar_bg);
-        ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams();
-        navBarBgLp.height = insets.bottom;
-        navBarBg.setLayoutParams(navBarBgLp);
-
+        mNavBarScrimHeight = insets.bottom;
         InsettableFrameLayout.dispatchInsets(this, insets);
     }
 
-    public SpringAnimationHandler getSpringAnimationHandler() {
-        return mUsingTabs ? null : mAH[AdapterHolder.MAIN].animationHandler;
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+
+        if (mNavBarScrimHeight > 0) {
+            canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
+                    mNavBarScrimPaint);
+        }
     }
 
     private void rebindAdapters(boolean showTabs) {
@@ -338,8 +317,6 @@
 
         mAllAppsStore.registerIconContainer(mAH[AdapterHolder.MAIN].recyclerView);
         mAllAppsStore.registerIconContainer(mAH[AdapterHolder.WORK].recyclerView);
-
-        applyTouchDelegate();
     }
 
     private void replaceRVContainer(boolean showTabs) {
@@ -370,7 +347,6 @@
     public void onTabChanged(int pos) {
         mHeader.setMainActive(pos == 0);
         reset();
-        applyTouchDelegate();
         if (mAH[pos].recyclerView != null) {
             mAH[pos].recyclerView.bindFastScrollbar();
 
@@ -457,7 +433,6 @@
 
         public final AllAppsGridAdapter adapter;
         final LinearLayoutManager layoutManager;
-        final SpringAnimationHandler animationHandler;
         final AlphabeticalAppsList appsList;
         final Rect padding = new Rect();
         AllAppsRecyclerView recyclerView;
@@ -465,25 +440,21 @@
 
         AdapterHolder(boolean isWork) {
             appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, isWork);
-            adapter = new AllAppsGridAdapter(mLauncher, appsList, mLauncher,
-                    AllAppsContainerView.this, true);
+            adapter = new AllAppsGridAdapter(mLauncher, appsList);
             appsList.setAdapter(adapter);
-            animationHandler = adapter.getSpringAnimationHandler();
             layoutManager = adapter.getLayoutManager();
         }
 
         void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) {
             appsList.updateItemFilter(matcher);
             recyclerView = (AllAppsRecyclerView) rv;
+            recyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
             recyclerView.setApps(appsList, mUsingTabs);
             recyclerView.setLayoutManager(layoutManager);
             recyclerView.setAdapter(adapter);
             recyclerView.setHasFixedSize(true);
             // No animations will occur when changes occur to the items in this RecyclerView.
             recyclerView.setItemAnimator(null);
-            if (FeatureFlags.LAUNCHER3_PHYSICS && animationHandler != null) {
-                recyclerView.setSpringAnimationHandler(animationHandler);
-            }
             FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(recyclerView);
             recyclerView.addItemDecoration(focusedItemDecorator);
             adapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index a61521c..27fc53a 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -18,8 +18,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.support.animation.DynamicAnimation;
-import android.support.animation.SpringAnimation;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
@@ -38,11 +36,10 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-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.touch.ItemClickHandler;
+import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.PackageManagerHelper;
 
 import java.util.List;
@@ -71,7 +68,6 @@
     // Common view type masks
     public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
     public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
-    public static final int VIEW_TYPE_MASK_HAS_SPRINGS = VIEW_TYPE_MASK_ICON;
 
 
     public interface BindViewCallback {
@@ -182,8 +178,6 @@
     private final AlphabeticalAppsList mApps;
     private final GridLayoutManager mGridLayoutMgr;
     private final GridSpanSizer mGridSizer;
-    private final View.OnClickListener mIconClickListener;
-    private final View.OnLongClickListener mIconLongClickListener;
 
     private final int mAppsPerRow;
 
@@ -195,10 +189,7 @@
     // The intent to send off to the market app, updated each time the search query changes.
     private Intent mMarketSearchIntent;
 
-    private final SpringAnimationHandler<ViewHolder> mSpringAnimationHandler;
-
-    public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps, View.OnClickListener
-            iconClickListener, View.OnLongClickListener iconLongClickListener, boolean springAnim) {
+    public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps) {
         Resources res = launcher.getResources();
         mLauncher = launcher;
         mApps = apps;
@@ -207,23 +198,11 @@
         mGridLayoutMgr = new AppsGridLayoutManager(launcher);
         mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
         mLayoutInflater = LayoutInflater.from(launcher);
-        mIconClickListener = iconClickListener;
-        mIconLongClickListener = iconLongClickListener;
-        if (FeatureFlags.LAUNCHER3_PHYSICS && springAnim) {
-            mSpringAnimationHandler = new SpringAnimationHandler<>(
-                    SpringAnimationHandler.Y_DIRECTION, new AllAppsSpringAnimationFactory());
-        } else {
-            mSpringAnimationHandler = null;
-        }
 
         mAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns;
         mGridLayoutMgr.setSpanCount(mAppsPerRow);
     }
 
-    public SpringAnimationHandler getSpringAnimationHandler() {
-        return mSpringAnimationHandler;
-    }
-
     public static boolean isDividerViewType(int viewType) {
         return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER);
     }
@@ -270,8 +249,8 @@
             case VIEW_TYPE_ICON:
                 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
                         R.layout.all_apps_icon, parent, false);
-                icon.setOnClickListener(mIconClickListener);
-                icon.setOnLongClickListener(mIconLongClickListener);
+                icon.setOnClickListener(ItemClickHandler.INSTANCE);
+                icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
                 icon.setLongPressTimeout(ViewConfiguration.getLongPressTimeout());
                 icon.setOnFocusChangeListener(mIconFocusListener);
 
@@ -344,22 +323,6 @@
     }
 
     @Override
-    public void onViewAttachedToWindow(ViewHolder holder) {
-        int type = holder.getItemViewType();
-        if (mSpringAnimationHandler != null && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
-            mSpringAnimationHandler.add(holder.itemView, holder);
-        }
-    }
-
-    @Override
-    public void onViewDetachedFromWindow(ViewHolder holder) {
-        int type = holder.getItemViewType();
-        if (mSpringAnimationHandler != null && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
-            mSpringAnimationHandler.remove(holder.itemView);
-        }
-    }
-
-    @Override
     public boolean onFailedToRecycleView(ViewHolder holder) {
         // Always recycle and we will reset the view when it is bound
         return true;
@@ -376,104 +339,4 @@
         return item.viewType;
     }
 
-    /**
-     * Helper class to set the SpringAnimation values for an item in the adapter.
-     */
-    private class AllAppsSpringAnimationFactory
-            implements SpringAnimationHandler.AnimationFactory<ViewHolder> {
-        private static final float DEFAULT_MAX_VALUE_PX = 100;
-        private static final float DEFAULT_MIN_VALUE_PX = -DEFAULT_MAX_VALUE_PX;
-
-        // Damping ratio range is [0, 1]
-        private static final float SPRING_DAMPING_RATIO = 0.55f;
-
-        // Stiffness is a non-negative number.
-        private static final float MIN_SPRING_STIFFNESS = 580f;
-        private static final float MAX_SPRING_STIFFNESS = 900f;
-
-        // The amount by which each adjacent rows' stiffness will differ.
-        private static final float ROW_STIFFNESS_COEFFICIENT = 50f;
-
-        // The percentage by which we multiply each row to create the row factor.
-        private static final float ROW_PERCENTAGE = 0.3f;
-
-        @Override
-        public SpringAnimation initialize(ViewHolder vh) {
-            return SpringAnimationHandler.forView(vh.itemView, DynamicAnimation.TRANSLATION_Y, 0);
-        }
-
-        /**
-         * @param spring A new or recycled SpringAnimation.
-         * @param vh The ViewHolder that {@param spring} is related to.
-         */
-        @Override
-        public void update(SpringAnimation spring, ViewHolder vh) {
-            int appPosition = vh.getAdapterPosition();
-            int col = appPosition % mAppsPerRow;
-            int row = appPosition / mAppsPerRow;
-
-            int numTotalRows = mApps.getNumAppRows() - 1; // zero-based count
-            if (row > (numTotalRows / 2)) {
-                // Mirror the rows so that the top row acts the same as the bottom row.
-                row = Math.abs(numTotalRows - row);
-            }
-
-            calculateSpringValues(spring, row, col);
-        }
-
-        @Override
-        public void setDefaultValues(SpringAnimation spring) {
-            calculateSpringValues(spring, 0, mAppsPerRow / 2);
-        }
-
-        /**
-         * We manipulate the stiffness, min, and max values based on the items distance to the
-         * first row and the items distance to the center column to create the ^-shaped motion
-         * effect.
-         */
-        private void calculateSpringValues(SpringAnimation spring, int row, int col) {
-            float rowFactor = (1 + row) * ROW_PERCENTAGE;
-            float colFactor = getColumnFactor(col, mAppsPerRow);
-
-            float minValue = DEFAULT_MIN_VALUE_PX * (rowFactor + colFactor);
-            float maxValue = DEFAULT_MAX_VALUE_PX * (rowFactor + colFactor);
-
-            float stiffness = Utilities.boundToRange(
-                    MAX_SPRING_STIFFNESS - (row * ROW_STIFFNESS_COEFFICIENT),
-                    MIN_SPRING_STIFFNESS,
-                    MAX_SPRING_STIFFNESS);
-
-            spring.setMinValue(minValue)
-                    .setMaxValue(maxValue)
-                    .getSpring()
-                    .setStiffness(stiffness)
-                    .setDampingRatio(SPRING_DAMPING_RATIO);
-        }
-
-        /**
-         * Increase the column factor as the distance increases between the column and the center
-         * column(s).
-         */
-        private float getColumnFactor(int col, int numCols) {
-            float centerColumn = numCols / 2;
-            int distanceToCenter = (int) Math.abs(col - centerColumn);
-
-            boolean evenNumberOfColumns = numCols % 2 == 0;
-            if (evenNumberOfColumns && col < centerColumn) {
-                distanceToCenter -= 1;
-            }
-
-            float factor = 0;
-            while (distanceToCenter > 0) {
-                if (distanceToCenter == 1) {
-                    factor += 0.2f;
-                } else {
-                    factor += 0.1f;
-                }
-                --distanceToCenter;
-            }
-
-            return factor;
-        }
-    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 53d19eb..a7447b7 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -17,14 +17,12 @@
 
 import static android.view.View.MeasureSpec.UNSPECIFIED;
 
-import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
-import android.util.Property;
 import android.util.SparseIntArray;
 import android.view.MotionEvent;
 import android.view.View;
@@ -35,12 +33,8 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
-import com.android.launcher3.anim.SpringAnimationHandler;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
-import com.android.launcher3.touch.OverScroll;
-import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.views.RecyclerViewFastScroller;
@@ -64,23 +58,6 @@
     private AllAppsBackgroundDrawable mEmptySearchBackground;
     private int mEmptySearchBackgroundTopOffset;
 
-    private SpringAnimationHandler mSpringAnimationHandler;
-    private OverScrollHelper mOverScrollHelper;
-    private SwipeDetector mPullDetector;
-    private float mContentTranslationY = 0;
-    public static final Property<AllAppsRecyclerView, Float> CONTENT_TRANS_Y =
-            new Property<AllAppsRecyclerView, Float>(Float.class, "appsRecyclerViewContentTransY") {
-                @Override
-                public Float get(AllAppsRecyclerView allAppsRecyclerView) {
-                    return allAppsRecyclerView.getContentTranslationY();
-                }
-
-                @Override
-                public void set(AllAppsRecyclerView allAppsRecyclerView, Float y) {
-                    allAppsRecyclerView.setContentTranslationY(y);
-                }
-            };
-
     public AllAppsRecyclerView(Context context) {
         this(context, null);
     }
@@ -97,33 +74,11 @@
             int defStyleRes) {
         super(context, attrs, defStyleAttr);
         Resources res = getResources();
-        addOnItemTouchListener(this);
         mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
                 R.dimen.all_apps_empty_search_bg_top_offset);
-
-        mOverScrollHelper = new OverScrollHelper();
-        mPullDetector = new SwipeDetector(getContext(), mOverScrollHelper, SwipeDetector.VERTICAL);
-        mPullDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, true);
-
         mNumAppsPerRow = LauncherAppState.getIDP(context).numColumns;
     }
 
-    public void setSpringAnimationHandler(SpringAnimationHandler springAnimationHandler) {
-        if (FeatureFlags.LAUNCHER3_PHYSICS) {
-            mSpringAnimationHandler = springAnimationHandler;
-            addOnScrollListener(new SpringMotionOnScrollListener());
-        }
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent e) {
-        mPullDetector.onTouchEvent(e);
-        if (FeatureFlags.LAUNCHER3_PHYSICS && mSpringAnimationHandler != null) {
-            mSpringAnimationHandler.addMovement(e);
-        }
-        return super.onTouchEvent(e);
-    }
-
     /**
      * Sets the list of apps in this view, used to determine the fastscroll position.
      */
@@ -171,26 +126,6 @@
     }
 
     @Override
-    protected void dispatchDraw(Canvas canvas) {
-        canvas.translate(0, mContentTranslationY);
-        super.dispatchDraw(canvas);
-        canvas.translate(0, -mContentTranslationY);
-    }
-
-    public float getContentTranslationY() {
-        return mContentTranslationY;
-    }
-
-    /**
-     * Use this method instead of calling {@link #setTranslationY(float)}} directly to avoid drawing
-     * on top of other Views.
-     */
-    public void setContentTranslationY(float y) {
-        mContentTranslationY = y;
-        invalidate();
-    }
-
-    @Override
     protected boolean verifyDrawable(Drawable who) {
         return who == mEmptySearchBackground || super.verifyDrawable(who);
     }
@@ -232,8 +167,7 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent e) {
-        mPullDetector.onTouchEvent(e);
-        boolean result = super.onInterceptTouchEvent(e) || mOverScrollHelper.isInOverScroll();
+        boolean result = super.onInterceptTouchEvent(e);
         if (!result && e.getAction() == MotionEvent.ACTION_DOWN
                 && mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
             mEmptySearchBackground.setHotspot(e.getX(), e.getY());
@@ -481,114 +415,4 @@
                 y + mEmptySearchBackground.getIntrinsicHeight());
     }
 
-    private class SpringMotionOnScrollListener extends RecyclerView.OnScrollListener {
-
-        @Override
-        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
-            if (mOverScrollHelper.isInOverScroll()) {
-                // OverScroll will handle animating the springs.
-                return;
-            }
-
-            // We only start the spring animation when we hit the top/bottom, to ensure
-            // that all of the animations start at the same time.
-            if (dy < 0 && !canScrollVertically(-1)) {
-                mSpringAnimationHandler.animateToFinalPosition(0, 1);
-            } else if (dy > 0 && !canScrollVertically(1)) {
-                mSpringAnimationHandler.animateToFinalPosition(0, -1);
-            }
-        }
-    }
-
-    private class OverScrollHelper implements SwipeDetector.Listener {
-
-        private static final float MAX_RELEASE_VELOCITY = 5000; // px / s
-        private static final float MAX_OVERSCROLL_PERCENTAGE = 0.07f;
-
-        private boolean mIsInOverScroll;
-
-        // We use this value to calculate the actual amount the user has overscrolled.
-        private float mFirstDisplacement = 0;
-
-        private boolean mAlreadyScrollingUp;
-        private int mFirstScrollYOnScrollUp;
-
-        @Override
-        public void onDragStart(boolean start) {
-        }
-
-        @Override
-        public boolean onDrag(float displacement, float velocity) {
-            boolean isScrollingUp = displacement > 0;
-            if (isScrollingUp) {
-                if (!mAlreadyScrollingUp) {
-                    mFirstScrollYOnScrollUp = getCurrentScrollY();
-                    mAlreadyScrollingUp = true;
-                }
-            } else {
-                mAlreadyScrollingUp = false;
-            }
-
-            // Only enter overscroll if the user is interacting with the RecyclerView directly
-            // and if one of the following criteria are met:
-            // - User scrolls down when they're already at the bottom.
-            // - User starts scrolling up, hits the top, and continues scrolling up.
-            boolean wasInOverScroll = mIsInOverScroll;
-            mIsInOverScroll = !mScrollbar.isDraggingThumb() &&
-                    ((!canScrollVertically(1) && displacement < 0) ||
-                    (!canScrollVertically(-1) && isScrollingUp && mFirstScrollYOnScrollUp != 0));
-
-            if (wasInOverScroll && !mIsInOverScroll) {
-                // Exit overscroll. This can happen when the user is in overscroll and then
-                // scrolls the opposite way.
-                reset(false /* shouldSpring */);
-            } else if (mIsInOverScroll) {
-                if (Float.compare(mFirstDisplacement, 0) == 0) {
-                    // Because users can scroll before entering overscroll, we need to
-                    // subtract the amount where the user was not in overscroll.
-                    mFirstDisplacement = displacement;
-                }
-                float overscrollY = displacement - mFirstDisplacement;
-                setContentTranslationY(getDampedOverScroll(overscrollY));
-            }
-
-            return mIsInOverScroll;
-        }
-
-        @Override
-        public void onDragEnd(float velocity, boolean fling) {
-           reset(mIsInOverScroll  /* shouldSpring */);
-        }
-
-        private void reset(boolean shouldSpring) {
-            float y = getContentTranslationY();
-            if (Float.compare(y, 0) != 0) {
-                if (mSpringAnimationHandler != null && shouldSpring) {
-                    // We calculate our own velocity to give the springs the desired effect.
-                    float velocity = y / getDampedOverScroll(getHeight()) * MAX_RELEASE_VELOCITY;
-                    // We want to negate the velocity because we are moving to 0 from -1 due to the
-                    // downward motion. (y-axis -1 is above 0).
-                    mSpringAnimationHandler.animateToPositionWithVelocity(0, -1, -velocity);
-                }
-
-                ObjectAnimator.ofFloat(AllAppsRecyclerView.this,
-                        AllAppsRecyclerView.CONTENT_TRANS_Y, 0)
-                        .setDuration(100)
-                        .start();
-            }
-            mIsInOverScroll = false;
-            mFirstDisplacement = 0;
-            mFirstScrollYOnScrollUp = 0;
-            mAlreadyScrollingUp = false;
-        }
-
-        public boolean isInOverScroll() {
-            return mIsInOverScroll;
-        }
-
-        private float getDampedOverScroll(float y) {
-            return OverScroll.dampedScroll(y, getHeight());
-        }
-    }
-
 }
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 416469f..a0dc5a3 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -27,7 +27,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
 
 import com.android.launcher3.R;
 
@@ -68,6 +67,7 @@
     private int mTranslationY;
     private boolean mForwardToRecyclerView;
 
+    protected boolean mTabsHidden;
     protected int mMaxTranslation;
 
     public FloatingHeaderView(@NonNull Context context) {
@@ -85,6 +85,7 @@
     }
 
     public void setup(AllAppsContainerView.AdapterHolder[] mAH, boolean tabsHidden) {
+        mTabsHidden = tabsHidden;
         mTabLayout.setVisibility(tabsHidden ? View.GONE : View.VISIBLE);
         mMainRV = setupRV(mMainRV, mAH[AllAppsContainerView.AdapterHolder.MAIN].recyclerView);
         mWorkRV = setupRV(mWorkRV, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView);
@@ -105,7 +106,13 @@
     }
 
     public int getMaxTranslation() {
-        return mMaxTranslation;
+        if (mMaxTranslation == 0 && mTabsHidden) {
+            return getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_bottom_padding);
+        } else if (mMaxTranslation > 0 && mTabsHidden) {
+            return mMaxTranslation + getPaddingTop();
+        } else {
+            return mMaxTranslation;
+        }
     }
 
     private boolean canSnapAt(int currentScrollY) {
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index bb17ed5..d8568f8 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -37,7 +37,7 @@
     /**
      * Notifies the search manager to close any active search session.
      */
-    void reset();
+    void resetSearch();
 
     /**
      * Called before dispatching a key event, in case the search manager wants to initialize
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index a56c8b8..dd80dac 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -15,6 +15,12 @@
  */
 package com.android.launcher3.allapps.search;
 
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.getSize;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static com.android.launcher3.graphics.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
+
 import android.content.Context;
 import android.graphics.Rect;
 import android.support.animation.FloatValueHolder;
@@ -29,7 +35,7 @@
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.View;
-import android.widget.FrameLayout;
+import android.view.ViewGroup.MarginLayoutParams;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.ExtendedEditText;
@@ -47,20 +53,16 @@
 /**
  * Layout to contain the All-apps search UI.
  */
-public class AppsSearchContainerLayout extends FrameLayout
+public class AppsSearchContainerLayout extends ExtendedEditText
         implements SearchUiManager, AllAppsSearchBarController.Callbacks,
         AllAppsStore.OnUpdateListener {
 
+
     private final Launcher mLauncher;
-    private final int mMinHeight;
-    private final int mSearchBoxHeight;
     private final AllAppsSearchBarController mSearchBarController;
     private final SpannableStringBuilder mSearchQueryBuilder;
 
-    private ExtendedEditText mSearchInput;
     private AlphabeticalAppsList mApps;
-    private View mDivider;
-    private HeaderElevationController mElevationController;
     private AllAppsContainerView mAppsView;
     private SpringAnimation mSpring;
 
@@ -76,39 +78,22 @@
         super(context, attrs, defStyleAttr);
 
         mLauncher = Launcher.getLauncher(context);
-        mMinHeight = getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_height);
-        mSearchBoxHeight = getResources()
-                .getDimensionPixelSize(R.dimen.all_apps_search_bar_field_height);
         mSearchBarController = new AllAppsSearchBarController();
 
         mSearchQueryBuilder = new SpannableStringBuilder();
         Selection.setSelection(mSearchQueryBuilder, 0);
 
-        // Note: This spring does nothing.
-        mSpring = new SpringAnimation(new FloatValueHolder()).setSpring(new SpringForce(0));
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mSearchInput = findViewById(R.id.search_box_input);
-        mDivider = findViewById(R.id.search_divider);
-        mElevationController = new HeaderElevationController(mDivider);
-
         // Update the hint to contain the icon.
         // Prefix the original hint with two spaces. The first space gets replaced by the icon
         // using span. The second space is used for a singe space character between the hint
         // and the icon.
-        SpannableString spanned = new SpannableString("  " + mSearchInput.getHint());
+        SpannableString spanned = new SpannableString("  " + getHint());
         spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search),
                 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
-        mSearchInput.setHint(spanned);
+        setHint(spanned);
 
-        DeviceProfile dp = mLauncher.getDeviceProfile();
-        if (!dp.isVerticalBarLayout()) {
-            LayoutParams lp = (LayoutParams) mDivider.getLayoutParams();
-            lp.leftMargin = lp.rightMargin = dp.edgeMarginPx;
-        }
+        // Note: This spring does nothing.
+        mSpring = new SpringAnimation(new FloatValueHolder()).setSpring(new SpringForce(0));
     }
 
     @Override
@@ -125,21 +110,39 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Update the width to match the grid padding
         DeviceProfile dp = mLauncher.getDeviceProfile();
-        if (!dp.isVerticalBarLayout()) {
-            getLayoutParams().height = dp.getInsets().top + mMinHeight;
-        }
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int myRequestedWidth = getSize(widthMeasureSpec);
+        int rowWidth = myRequestedWidth - mAppsView.getActiveRecyclerView().getPaddingLeft()
+                - mAppsView.getActiveRecyclerView().getPaddingRight();
+
+        int cellWidth = DeviceProfile.calculateCellWidth(rowWidth, dp.inv.numHotseatIcons);
+        int iconVisibleSize = Math.round(ICON_VISIBLE_AREA_FACTOR * dp.iconSizePx);
+        int iconPadding = cellWidth - iconVisibleSize;
+
+        int myWidth = rowWidth - iconPadding + getPaddingLeft() + getPaddingRight();
+        super.onMeasure(makeMeasureSpec(myWidth, EXACTLY), heightMeasureSpec);
     }
 
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        // Shift the widget horizontally so that its centered in the parent (b/63428078)
+        View parent = (View) getParent();
+        int availableWidth = parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight();
+        int myWidth = right - left;
+        int expectedLeft = parent.getPaddingLeft() + (availableWidth - myWidth) / 2;
+        int shift = expectedLeft - left;
+        setTranslationX(shift);
+    }
 
     @Override
     public void initialize(AllAppsContainerView appsView) {
         mApps = appsView.getApps();
         mAppsView = appsView;
-        appsView.addElevationController(mElevationController);
         mSearchBarController.initialize(
-                new DefaultAppSearchAlgorithm(mApps.getApps()), mSearchInput, mLauncher, this);
+                new DefaultAppSearchAlgorithm(mApps.getApps()), this, mLauncher, this);
     }
 
     @Override
@@ -153,8 +156,7 @@
     }
 
     @Override
-    public void reset() {
-        mElevationController.reset();
+    public void resetSearch() {
         mSearchBarController.reset();
     }
 
@@ -200,7 +202,6 @@
     }
 
     private void notifyResultChanged() {
-        mElevationController.reset();
         mAppsView.onSearchResultsChanged();
     }
 
@@ -214,9 +215,9 @@
                 if (!dp.isVerticalBarLayout()) {
                     Rect insets = dp.getInsets();
                     int hotseatBottom = bottom - dp.hotseatBarBottomPaddingPx - insets.bottom;
-                    int searchTopMargin = insets.top + (mMinHeight - mSearchBoxHeight)
-                            + ((MarginLayoutParams) getLayoutParams()).bottomMargin;
-                    listener.onScrollRangeChanged(hotseatBottom - searchTopMargin);
+                    MarginLayoutParams mlp = ((MarginLayoutParams) getLayoutParams());
+                    int myBot = mlp.topMargin + (int) getTranslationY() + mlp.height;
+                    listener.onScrollRangeChanged(hotseatBottom - myBot);
                 } else {
                     listener.onScrollRangeChanged(bottom);
                 }
diff --git a/src/com/android/launcher3/allapps/search/HeaderElevationController.java b/src/com/android/launcher3/allapps/search/HeaderElevationController.java
deleted file mode 100644
index 7cd32b2..0000000
--- a/src/com/android/launcher3/allapps/search/HeaderElevationController.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.android.launcher3.allapps.search;
-
-import android.content.res.Resources;
-import android.graphics.Outline;
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-
-import com.android.launcher3.BaseRecyclerView;
-import com.android.launcher3.R;
-
-/**
- * Helper class for controlling the header elevation in response to RecyclerView scroll.
- */
-public class HeaderElevationController extends RecyclerView.OnScrollListener {
-
-    private final View mHeader;
-    private final View mHeaderChild;
-    private final float mMaxElevation;
-    private final float mScrollToElevation;
-
-    private int mCurrentY = 0;
-
-    public HeaderElevationController(View header) {
-        mHeader = header;
-        final Resources res = mHeader.getContext().getResources();
-        mMaxElevation = res.getDimension(R.dimen.all_apps_header_max_elevation);
-        mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
-
-        // We need to provide a custom outline so the shadow only appears on the bottom edge.
-        // The top, left and right edges are all extended out to match parent's edge, so that
-        // the shadow is clipped by the parent.
-        final ViewOutlineProvider vop = new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                // Set the left and top to be at the parents edge. Since the coordinates are
-                // relative to this view,
-                //    (x = -view.getLeft()) for this view => (x = 0) for parent
-                final int left = -view.getLeft();
-                final int top = -view.getTop();
-
-                // Since the view is centered align, the spacing on left and right are same.
-                // Add same spacing on the right to reach parent's edge.
-                final int right = view.getWidth() - left;
-                final int bottom = view.getHeight();
-                final int offset = (int) mMaxElevation;
-                outline.setRect(left - offset, top - offset, right + offset, bottom);
-            }
-        };
-        mHeader.setOutlineProvider(vop);
-        mHeaderChild = ((ViewGroup) mHeader).getChildAt(0);
-    }
-
-    public void reset() {
-        mCurrentY = 0;
-        onScroll(mCurrentY);
-    }
-
-    @Override
-    public final void onScrolled(RecyclerView recyclerView, int dx, int dy) {
-        mCurrentY = ((BaseRecyclerView) recyclerView).getCurrentScrollY();
-        onScroll(mCurrentY);
-    }
-
-    private void onScroll(int scrollY) {
-        float elevationPct = Math.min(scrollY, mScrollToElevation) / mScrollToElevation;
-        float newElevation = mMaxElevation * elevationPct;
-        if (Float.compare(mHeader.getElevation(), newElevation) != 0) {
-            mHeader.setElevation(newElevation);
-
-            // To simulate a scrolling effect for the header, we translate the header down, and
-            // its content up by the same amount, so that it gets clipped by the parent, making it
-            // look like the content was scrolled out of the view.
-            int shift = Math.min(mHeader.getHeight(), scrollY);
-            mHeader.setTranslationY(-shift);
-            mHeaderChild.setTranslationY(shift);
-        }
-    }
-
-}
diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java
deleted file mode 100644
index 29a2430..0000000
--- a/src/com/android/launcher3/anim/SpringAnimationHandler.java
+++ /dev/null
@@ -1,265 +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.anim;
-
-import android.support.animation.FloatPropertyCompat;
-import android.support.animation.SpringAnimation;
-import android.support.animation.SpringForce;
-import android.support.annotation.IntDef;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-
-import com.android.launcher3.R;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-
-/**
- * Handler class that manages springs for a set of views that should all move based on the same
- * {@link MotionEvent}s.
- *
- * Supports setting either X or Y velocity on the list of springs added to this handler.
- */
-public class SpringAnimationHandler<T> {
-
-    private static final String TAG = "SpringAnimationHandler";
-    private static final boolean DEBUG = false;
-
-    private static final float VELOCITY_DAMPING_FACTOR = 0.175f;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({Y_DIRECTION, X_DIRECTION})
-    public @interface Direction {}
-    public static final int Y_DIRECTION = 0;
-    public static final int X_DIRECTION = 1;
-    private int mVelocityDirection;
-
-    private VelocityTracker mVelocityTracker;
-    private float mCurrentVelocity = 0;
-    private boolean mShouldComputeVelocity = false;
-
-    private AnimationFactory<T> mAnimationFactory;
-
-    private ArrayList<SpringAnimation> mAnimations = new ArrayList<>();
-
-    /**
-     * @param direction Either {@link #X_DIRECTION} or {@link #Y_DIRECTION}.
-     *                  Determines which direction we use to calculate and set the velocity.
-     * @param factory   The AnimationFactory is responsible for initializing and updating the
-     *                  SpringAnimations added to this class.
-     */
-    public SpringAnimationHandler(@Direction int direction, AnimationFactory<T> factory) {
-        mVelocityDirection = direction;
-        mAnimationFactory = factory;
-    }
-
-    /**
-     * Adds a spring to the list of springs handled by this class.
-     * @param spring The new spring to be added.
-     * @param setDefaultValues If True, sets the spring to the default
-     *                         {@link AnimationFactory} values.
-     */
-    public void add(SpringAnimation spring, boolean setDefaultValues) {
-        if (setDefaultValues) {
-            mAnimationFactory.setDefaultValues(spring);
-        }
-        spring.setStartVelocity(mCurrentVelocity);
-        mAnimations.add(spring);
-    }
-
-    public AnimationFactory<T> getFactory() {
-        return mAnimationFactory;
-    }
-
-    /**
-     * Adds a new or recycled animation to the list of springs handled by this class.
-     *
-     * @param view The view the spring is attached to.
-     * @param object Used to initialize and update the spring.
-     */
-    public void add(View view, T object) {
-        SpringAnimation spring = (SpringAnimation) view.getTag(R.id.spring_animation_tag);
-        if (spring == null) {
-            spring = mAnimationFactory.initialize(object);
-            view.setTag(R.id.spring_animation_tag, spring);
-        }
-        mAnimationFactory.update(spring, object);
-        add(spring, false /* setDefaultValues */);
-    }
-
-    /**
-     * Stops and removes the spring attached to {@param view}.
-     */
-    public void remove(View view) {
-        remove((SpringAnimation) view.getTag(R.id.spring_animation_tag));
-    }
-
-    public void remove(SpringAnimation animation) {
-        if (animation.canSkipToEnd()) {
-            animation.skipToEnd();
-        }
-        mAnimations.remove(animation);
-    }
-
-    public void addMovement(MotionEvent event) {
-        int action = event.getActionMasked();
-        if (DEBUG) Log.d(TAG, "addMovement#action=" + action);
-        switch (action) {
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_DOWN:
-                reset();
-                break;
-        }
-
-        getVelocityTracker().addMovement(event);
-        mShouldComputeVelocity = true;
-    }
-
-    public void animateToFinalPosition(float position, int startValue) {
-        animateToFinalPosition(position, startValue, mShouldComputeVelocity);
-    }
-
-    /**
-     * @param position The final animation position.
-     * @param startValue < 0 if scrolling from start to end; > 0 if scrolling from end to start
-     *                   The magnitude of the number changes how the spring will move.
-     * @param setVelocity If true, we set the velocity to {@link #mCurrentVelocity} before
-     *                    starting the animation.
-     */
-    private void animateToFinalPosition(float position, int startValue, boolean setVelocity) {
-        if (DEBUG) {
-            Log.d(TAG, "animateToFinalPosition#position=" + position + ", startValue=" + startValue);
-        }
-
-        if (mShouldComputeVelocity) {
-            mCurrentVelocity = computeVelocity();
-        }
-
-        int size = mAnimations.size();
-        for (int i = 0; i < size; ++i) {
-            mAnimations.get(i).setStartValue(startValue);
-            if (setVelocity) {
-                mAnimations.get(i).setStartVelocity(mCurrentVelocity);
-            }
-            mAnimations.get(i).animateToFinalPosition(position);
-        }
-
-        reset();
-    }
-
-    /**
-     * Similar to {@link #animateToFinalPosition(float, int)}, but used in cases where we want to
-     * manually set the velocity.
-     */
-    public void animateToPositionWithVelocity(float position, int startValue, float velocity) {
-        if (DEBUG) {
-            Log.d(TAG, "animateToPosition#pos=" + position + ", start=" + startValue
-                    + ", velocity=" + velocity);
-        }
-
-        mCurrentVelocity = velocity;
-        mShouldComputeVelocity = false;
-        animateToFinalPosition(position, startValue, true);
-    }
-
-
-    public boolean isRunning() {
-        // All the animations run at the same time so we can just check the first one.
-        return !mAnimations.isEmpty() && mAnimations.get(0).isRunning();
-    }
-
-    public void skipToEnd() {
-        if (DEBUG) Log.d(TAG, "setStartVelocity#skipToEnd");
-        if (DEBUG) Log.v(TAG, "setStartVelocity#skipToEnd", new Exception());
-
-        int size = mAnimations.size();
-        for (int i = 0; i < size; ++i) {
-            if (mAnimations.get(i).canSkipToEnd()) {
-                mAnimations.get(i).skipToEnd();
-            }
-        }
-    }
-
-    public void reset() {
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
-        mCurrentVelocity = 0;
-        mShouldComputeVelocity = false;
-    }
-
-
-    private float computeVelocity() {
-        getVelocityTracker().computeCurrentVelocity(1000 /* millis */);
-
-        float velocity = isVerticalDirection()
-                ? getVelocityTracker().getYVelocity()
-                : getVelocityTracker().getXVelocity();
-        velocity *= VELOCITY_DAMPING_FACTOR;
-
-        if (DEBUG) Log.d(TAG, "computeVelocity=" + velocity);
-        return velocity;
-    }
-
-    private boolean isVerticalDirection() {
-        return mVelocityDirection == Y_DIRECTION;
-    }
-
-    private VelocityTracker getVelocityTracker() {
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        }
-        return mVelocityTracker;
-    }
-
-    /**
-     * This interface is used to initialize and update the SpringAnimations added to the
-     * {@link SpringAnimationHandler}.
-     *
-     * @param <T> The object that each SpringAnimation is attached to.
-     */
-    public interface AnimationFactory<T> {
-
-        /**
-         * Initializes a new Spring for {@param object}.
-         */
-        SpringAnimation initialize(T object);
-
-        /**
-         * Updates the value of {@param spring} based on {@param object}.
-         */
-        void update(SpringAnimation spring, T object);
-
-        /**
-         * Sets the factory default values for the given {@param spring}.
-         */
-        void setDefaultValues(SpringAnimation spring);
-    }
-
-    /**
-     * Helper method to create a new SpringAnimation for {@param view}.
-     */
-    public static SpringAnimation forView(View view, FloatPropertyCompat property, float finalPos) {
-        SpringAnimation spring = new SpringAnimation(view, property, finalPos);
-        spring.setSpring(new SpringForce(finalPos));
-        return spring;
-    }
-
-}
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index e494bea..28645dc 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -35,8 +35,6 @@
     public static final boolean LAUNCHER3_DIRECT_SCROLL = true;
     // When enabled the promise icon is visible in all apps while installation an app.
     public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
-    // When enabled allows use of physics based motions in the Launcher.
-    public static final boolean LAUNCHER3_PHYSICS = true;
     // When enabled allows use of spring motions on the icons.
     public static final boolean LAUNCHER3_SPRING_ICONS = true;
 
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index db199c1..95e1034 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -16,6 +16,11 @@
 
 package com.android.launcher3.dragndrop;
 
+import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
 import android.appwidget.AppWidgetManager;
@@ -23,7 +28,6 @@
 import android.content.ClipDescription;
 import android.content.Intent;
 import android.content.pm.LauncherApps.PinItemRequest;
-import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -43,7 +47,6 @@
 import com.android.launcher3.LauncherAppWidgetHost;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompatVO;
 import com.android.launcher3.model.WidgetItem;
@@ -55,11 +58,6 @@
 import com.android.launcher3.widget.WidgetHostViewLoader;
 import com.android.launcher3.widget.WidgetImageView;
 
-import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
-import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
-
 @TargetApi(Build.VERSION_CODES.O)
 public class AddItemActivity extends BaseActivity implements OnLongClickListener, OnTouchListener {
 
@@ -154,15 +152,7 @@
                         .setPackage(getPackageName())
                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
 
-        if (!getResources().getBoolean(R.bool.allow_rotation) &&
-                !Utilities.isAllowRotationPrefEnabled(this) &&
-                (getResources().getConfiguration().orientation ==
-                        Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode())) {
-            // If we are starting the drag in landscape even though home is locked in portrait,
-            // restart the home activity to temporarily allow rotation.
-            homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        }
-
+        listener.initWhenReady();
         startActivity(homeIntent,
                 ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
         mFinishOnPause = true;
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index 9638a75..df4a7c1 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -17,6 +17,8 @@
 package com.android.launcher3.dragndrop;
 
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
+import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
 
 import android.content.ClipDescription;
 import android.content.Intent;
@@ -79,6 +81,7 @@
         AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
         launcher.getStateManager().goToState(NORMAL, alreadyOnHome /* animated */);
         launcher.getDragLayer().setOnDragListener(this);
+        launcher.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
 
         mLauncher = launcher;
         mDragController = launcher.getDragController();
@@ -157,6 +160,7 @@
     }
 
     private void postCleanup() {
+        clearReference();
         if (mLauncher != null) {
             // Remove any drag params from the launcher intent since the drag operation is complete.
             Intent newIntent = new Intent(mLauncher.getIntent());
@@ -164,16 +168,12 @@
             mLauncher.setIntent(newIntent);
         }
 
-        new Handler(Looper.getMainLooper()).post(new Runnable() {
-            @Override
-            public void run() {
-                removeListener();
-            }
-        });
+        new Handler(Looper.getMainLooper()).post(this::removeListener);
     }
 
     public void removeListener() {
         if (mLauncher != null) {
+            mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_NONE);
             mLauncher.getDragLayer().setOnDragListener(null);
         }
     }
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 301070c..f5d0b24 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -709,12 +709,14 @@
     public void onViewAdded(View child) {
         super.onViewAdded(child);
         updateChildIndices();
+        UiFactory.onLauncherStateOrFocusChanged(mLauncher);
     }
 
     @Override
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
         updateChildIndices();
+        UiFactory.onLauncherStateOrFocusChanged(mLauncher);
     }
 
     @Override
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 1c6f77c..5576d91 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -36,7 +36,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.PreviewBackground;
-import com.android.launcher3.uioverrides.UiFactory;
+import com.android.launcher3.graphics.BitmapRenderer;
 import com.android.launcher3.util.Preconditions;
 
 /**
@@ -113,7 +113,7 @@
         final float previewShiftX = shiftFactor * previewWidth;
         final float previewShiftY = shiftFactor * previewHeight;
 
-        Bitmap previewBitmap = UiFactory.createFromRenderer(previewWidth, previewHeight, false,
+        Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(previewWidth, previewHeight,
                 (canvas) -> {
                     int count = canvas.save();
                     canvas.translate(previewShiftX, previewShiftY);
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 13926db..6c94273 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -63,6 +63,7 @@
 import com.android.launcher3.dragndrop.BaseItemDragListener;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
@@ -159,14 +160,14 @@
                 .inflate(resId, group, false);
 
         icon.setClipToPadding(false);
-        icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
+        icon.mFolderName = icon.findViewById(R.id.folder_icon_name);
         icon.mFolderName.setText(folderInfo.title);
         icon.mFolderName.setCompoundDrawablePadding(0);
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
         lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
 
         icon.setTag(folderInfo);
-        icon.setOnClickListener(launcher);
+        icon.setOnClickListener(ItemClickHandler.INSTANCE);
         icon.mInfo = folderInfo;
         icon.mLauncher = launcher;
         icon.mBadgeRenderer = launcher.getDeviceProfile().mBadgeRenderer;
diff --git a/src/com/android/launcher3/graphics/BitmapRenderer.java b/src/com/android/launcher3/graphics/BitmapRenderer.java
index 4652ded..3d11c44 100644
--- a/src/com/android/launcher3/graphics/BitmapRenderer.java
+++ b/src/com/android/launcher3/graphics/BitmapRenderer.java
@@ -15,9 +15,40 @@
  */
 package com.android.launcher3.graphics;
 
+import android.annotation.TargetApi;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Picture;
+import android.os.Build;
 
-public interface BitmapRenderer {
+import com.android.launcher3.Utilities;
 
-     void render(Canvas out);
+public class BitmapRenderer {
+
+     public static final boolean USE_HARDWARE_BITMAP = Utilities.ATLEAST_P;
+
+     public static Bitmap createSoftwareBitmap(int width, int height, Renderer renderer) {
+          Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+          renderer.draw(new Canvas(result));
+          return result;
+     }
+
+     @TargetApi(Build.VERSION_CODES.P)
+     public static Bitmap createHardwareBitmap(int width, int height, Renderer renderer) {
+          if (!USE_HARDWARE_BITMAP) {
+               return createSoftwareBitmap(width, height, renderer);
+          }
+
+          Picture picture = new Picture();
+          renderer.draw(picture.beginRecording(width, height));
+          picture.endRecording();
+          return Bitmap.createBitmap(picture);
+     }
+
+     /**
+      * Interface representing a bitmap draw operation.
+      */
+     public interface Renderer {
+          void draw(Canvas out);
+     }
 }
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index b770785..e60a2c7 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -24,19 +24,17 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
-import android.graphics.Region.Op;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.view.View;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.UiThreadHelper;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
 import java.nio.ByteBuffer;
 
@@ -119,28 +117,26 @@
      * Responsibility for the bitmap is transferred to the caller.
      */
     public Bitmap createDragBitmap() {
-        float scale = 1f;
         int width = mView.getWidth();
         int height = mView.getHeight();
 
-        boolean forceSoftwareRenderer = false;
         if (mView instanceof BubbleTextView) {
             Drawable d = ((BubbleTextView) mView).getIcon();
             Rect bounds = getDrawableBounds(d);
             width = bounds.width();
             height = bounds.height();
         } else if (mView instanceof LauncherAppWidgetHostView) {
-            scale = ((LauncherAppWidgetHostView) mView).getScaleToFit();
+            float scale = ((LauncherAppWidgetHostView) mView).getScaleToFit();
             width = (int) (mView.getWidth() * scale);
             height = (int) (mView.getHeight() * scale);
 
             // Use software renderer for widgets as we know that they already work
-            forceSoftwareRenderer = true;
+            return BitmapRenderer.createSoftwareBitmap(width + blurSizeOutline,
+                    height + blurSizeOutline, (c) -> drawDragView(c, scale));
         }
 
-        final float scaleFinal = scale;
-        return UiFactory.createFromRenderer(width + blurSizeOutline, height + blurSizeOutline,
-                forceSoftwareRenderer, (c) -> drawDragView(c, scaleFinal));
+        return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
+                height + blurSizeOutline, (c) -> drawDragView(c, 1));
     }
 
     public final void generateDragOutline(Bitmap preview) {
diff --git a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
deleted file mode 100644
index ebfe1e7..0000000
--- a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2008 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.graphics;
-
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_ADAPTIVE_ICON;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BlurMaskFilter;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.SparseArray;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.ItemInfoWithIcon;
-import com.android.launcher3.R;
-import com.android.launcher3.uioverrides.UiFactory;
-
-/**
- * Utility class to generate shadow and outline effect, which are used for click feedback
- * and drag-n-drop respectively.
- */
-public class HolographicOutlineHelper {
-
-    /**
-     * Bitmap used as shadow for Adaptive icons
-     */
-    public static final Bitmap ADAPTIVE_ICON_SHADOW_BITMAP =
-            Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
-
-    private static HolographicOutlineHelper sInstance;
-
-    private final Canvas mCanvas = new Canvas();
-    private final Paint mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
-    private final Paint mErasePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
-
-    private final float mShadowBitmapShift;
-    private final BlurMaskFilter mShadowBlurMaskFilter;
-
-    // We have 4 different icon sizes: homescreen, hotseat, folder & all-apps
-    private final SparseArray<Bitmap> mBitmapCache = new SparseArray<>(4);
-
-    private HolographicOutlineHelper(Context context) {
-        mShadowBitmapShift = context.getResources().getDimension(R.dimen.blur_size_click_shadow);
-        mShadowBlurMaskFilter = new BlurMaskFilter(mShadowBitmapShift, BlurMaskFilter.Blur.NORMAL);
-        mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
-    }
-
-    public static HolographicOutlineHelper getInstance(Context context) {
-        if (sInstance == null) {
-            sInstance = new HolographicOutlineHelper(context.getApplicationContext());
-        }
-        return sInstance;
-    }
-
-    public Bitmap createMediumDropShadow(BubbleTextView view) {
-        if (view.getTag() instanceof ItemInfoWithIcon &&
-                ((((ItemInfoWithIcon) view.getTag()).runtimeStatusFlags & FLAG_ADAPTIVE_ICON)
-                        != 0)) {
-            return ADAPTIVE_ICON_SHADOW_BITMAP;
-        }
-        Drawable drawable = view.getIcon();
-        if (drawable == null) {
-            return null;
-        }
-
-        float scaleX = view.getScaleX();
-        float scaleY = view.getScaleY();
-        Rect rect = drawable.getBounds();
-
-        int bitmapWidth = (int) (rect.width() * scaleX);
-        int bitmapHeight = (int) (rect.height() * scaleY);
-        if (bitmapHeight <= 0 || bitmapWidth <= 0) {
-            return null;
-        }
-
-        int key = (bitmapWidth << 16) | bitmapHeight;
-        Bitmap cache = mBitmapCache.get(key);
-        if (cache == null) {
-            cache = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ALPHA_8);
-            mCanvas.setBitmap(cache);
-            mBitmapCache.put(key, cache);
-        } else {
-            mCanvas.setBitmap(cache);
-            mCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
-        }
-
-        int saveCount = mCanvas.save();
-        mCanvas.scale(scaleX, scaleY);
-        mCanvas.translate(-rect.left, -rect.top);
-        if (!UiFactory.USE_HARDWARE_BITMAP) {
-            // TODO: Outline generation requires alpha extraction, which is costly for
-            // hardware bitmaps. Instead use canvas layer operations once its available.
-            drawable.draw(mCanvas);
-        }
-        mCanvas.restoreToCount(saveCount);
-        mCanvas.setBitmap(null);
-
-        mBlurPaint.setMaskFilter(mShadowBlurMaskFilter);
-
-        int extraSize = (int) (2 * mShadowBitmapShift);
-
-        int resultWidth = bitmapWidth + extraSize;
-        int resultHeight = bitmapHeight + extraSize;
-        key = (resultWidth << 16) | resultHeight;
-        Bitmap result = mBitmapCache.get(key);
-        if (result == null) {
-            result = Bitmap.createBitmap(resultWidth, resultHeight, Bitmap.Config.ALPHA_8);
-            mCanvas.setBitmap(result);
-        } else {
-            // Use put instead of delete, to avoid unnecessary shrinking of cache array
-            mBitmapCache.put(key, null);
-            mCanvas.setBitmap(result);
-            mCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
-        }
-        mCanvas.drawBitmap(cache, mShadowBitmapShift, mShadowBitmapShift, mBlurPaint);
-        mCanvas.setBitmap(null);
-        return result;
-    }
-
-    public void recycleShadowBitmap(Bitmap bitmap) {
-        if (bitmap != null && bitmap != ADAPTIVE_ICON_SHADOW_BITMAP) {
-            mBitmapCache.put((bitmap.getWidth() << 16) | bitmap.getHeight(), bitmap);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java
index bd20c87..5d99ba0 100644
--- a/src/com/android/launcher3/graphics/IconNormalizer.java
+++ b/src/com/android/launcher3/graphics/IconNormalizer.java
@@ -61,6 +61,9 @@
     private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
     private static final float SCALE_NOT_INITIALIZED = 0;
 
+    // Ratio of the diameter of an normalized circular icon to the actual icon size.
+    public static final float ICON_VISIBLE_AREA_FACTOR = 0.92f;
+
     private final int mMaxSize;
     private final Bitmap mBitmap;
     private final Bitmap mBitmapARGB;
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 8abfdea..4a9cdd9 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -52,7 +52,6 @@
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
-import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.Themes;
 
@@ -349,7 +348,7 @@
         final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache);
 
         result.color = badge.iconColor;
-        result.icon = UiFactory.createFromRenderer(mIconBitmapSize, mIconBitmapSize, false, (c) -> {
+        result.icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> {
             getShadowGenerator().recreateIcon(unbadgedfinal, c);
             badgeWithDrawable(c, new FastBitmapDrawable(badge));
         });
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index ea55ba4..42ba191 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -162,9 +162,9 @@
     }
 
     @Override
-    public void draw(Canvas canvas) {
+    public void drawInternal(Canvas canvas, Rect bounds) {
         if (mRanFinishAnimation) {
-            super.draw(canvas);
+            super.drawInternal(canvas, bounds);
             return;
         }
 
@@ -172,15 +172,13 @@
         mProgressPaint.setColor(mIndicatorColor);
         mProgressPaint.setAlpha(mTrackAlpha);
         if (mShadowBitmap != null) {
-            canvas.drawBitmap(mShadowBitmap, getBounds().left, getBounds().top, mProgressPaint);
+            canvas.drawBitmap(mShadowBitmap, bounds.left, bounds.top, mProgressPaint);
         }
         canvas.drawPath(mScaledProgressPath, mProgressPaint);
 
         int saveCount = canvas.save();
-        Rect bounds = getBounds();
-
         canvas.scale(mIconScale, mIconScale, bounds.exactCenterX(), bounds.exactCenterY());
-        super.draw(canvas);
+        super.drawInternal(canvas, bounds);
         canvas.restoreToCount(saveCount);
     }
 
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index 9aa30e7..fcdc088 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -29,7 +29,6 @@
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.widget.WidgetListRowEntry;
-import com.android.launcher3.widget.WidgetsListAdapter;
 
 import java.util.ArrayList;
 import java.util.concurrent.Executor;
@@ -80,19 +79,18 @@
      */
     public final void scheduleCallbackTask(final CallbackTask task) {
         final Callbacks callbacks = mModel.getCallback();
-        mUiExecutor.execute(new Runnable() {
-            public void run() {
-                Callbacks cb = mModel.getCallback();
-                if (callbacks == cb && cb != null) {
-                    task.execute(callbacks);
-                }
+        mUiExecutor.execute(() -> {
+            Callbacks cb = mModel.getCallback();
+            if (callbacks == cb && cb != null) {
+                task.execute(callbacks);
             }
         });
     }
 
     public ModelWriter getModelWriter() {
-        // Updates from model task, do not deal with icon position in hotseat.
-        return mModel.getWriter(false /* hasVerticalHotseat */);
+        // Updates from model task, do not deal with icon position in hotseat. Also no need to
+        // verify changes as the ModelTasks always push the changes to callbacks
+        return mModel.getWriter(false /* hasVerticalHotseat */, false /* verifyChanges */);
     }
 
 
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 8640401..fff1e69 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -106,6 +106,11 @@
     public final WidgetsModel widgetsModel = new WidgetsModel();
 
     /**
+     * Id when the model was last bound
+     */
+    public int lastBindId = 0;
+
+    /**
      * Clears all the data
      */
     public synchronized void clear() {
diff --git a/src/com/android/launcher3/model/LoaderResults.java b/src/com/android/launcher3/model/LoaderResults.java
index 5acc790..5d4a352 100644
--- a/src/com/android/launcher3/model/LoaderResults.java
+++ b/src/com/android/launcher3/model/LoaderResults.java
@@ -25,7 +25,6 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherModel.Callbacks;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.MainThreadExecutor;
@@ -37,7 +36,6 @@
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.widget.WidgetListRowEntry;
-import com.android.launcher3.widget.WidgetsListAdapter;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -100,6 +98,7 @@
             workspaceItems.addAll(mBgDataModel.workspaceItems);
             appWidgets.addAll(mBgDataModel.appWidgets);
             orderedScreenIds.addAll(mBgDataModel.workspaceScreens);
+            mBgDataModel.lastBindId++;
         }
 
         final int currentScreen;
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index c615050..9d1ff83 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -16,6 +16,11 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -25,7 +30,6 @@
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.PackageInstaller;
 import android.graphics.Bitmap;
-import android.graphics.drawable.AdaptiveIconDrawable;
 import android.os.Handler;
 import android.os.Process;
 import android.os.UserHandle;
@@ -36,7 +40,6 @@
 
 import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.ClickShadowView;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.IconCache;
 import com.android.launcher3.InstallShortcutReceiver;
@@ -75,11 +78,6 @@
 import java.util.Map;
 import java.util.concurrent.CancellationException;
 
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
-
 /**
  * Runnable for the thread that loads the contents of the launcher:
  *   - workspace icons
@@ -146,9 +144,7 @@
 
         TraceHelper.beginSection(TAG);
         try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
-            TraceHelper.partitionSection(TAG, "step 1.1: loading UI resources");
-            loadUiResources();
-            TraceHelper.partitionSection(TAG, "step 1.2: loading workspace");
+            TraceHelper.partitionSection(TAG, "step 1.1: loading workspace");
             loadWorkspace();
 
             verifyNotStopped();
@@ -211,15 +207,6 @@
         this.notify();
     }
 
-    public void loadUiResources() {
-        if (Utilities.ATLEAST_OREO) {
-            LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
-            ClickShadowView.setAdaptiveIconScaleFactor(li.getNormalizer()
-                    .getScale(new AdaptiveIconDrawable(null, null), null, null, null));
-            li.recycle();
-        }
-    }
-
     private void loadWorkspace() {
         final Context context = mApp.getContext();
         final ContentResolver contentResolver = context.getContentResolver();
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 40e0f49..72c703b 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -21,12 +21,15 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
 import android.util.Log;
 
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.Callbacks;
 import com.android.launcher3.LauncherProvider;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -50,15 +53,23 @@
     public static final boolean DEBUG_DELETE = true;
 
     private final Context mContext;
+    private final LauncherModel mModel;
     private final BgDataModel mBgDataModel;
+    private final Handler mUiHandler;
+
     private final Executor mWorkerExecutor;
     private final boolean mHasVerticalHotseat;
+    private final boolean mVerifyChanges;
 
-    public ModelWriter(Context context, BgDataModel dataModel, boolean hasVerticalHotseat) {
+    public ModelWriter(Context context, LauncherModel model, BgDataModel dataModel,
+            boolean hasVerticalHotseat, boolean verifyChanges) {
         mContext = context;
+        mModel = model;
         mBgDataModel = dataModel;
         mWorkerExecutor = new LooperExecutor(LauncherModel.getWorkerLooper());
         mHasVerticalHotseat = hasVerticalHotseat;
+        mVerifyChanges = verifyChanges;
+        mUiHandler = new Handler(Looper.getMainLooper());
     }
 
     private void updateItemInfoProps(
@@ -214,15 +225,16 @@
         item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getLong(Settings.EXTRA_VALUE);
         writer.put(Favorites._ID, item.id);
 
-        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
-        mWorkerExecutor.execute(new Runnable() {
-            public void run() {
-                cr.insert(Favorites.CONTENT_URI, writer.getValues(mContext));
+        ModelVerifier verifier = new ModelVerifier();
 
-                synchronized (mBgDataModel) {
-                    checkItemInfoLocked(item.id, item, stackTrace);
-                    mBgDataModel.addItem(mContext, item, true);
-                }
+        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+        mWorkerExecutor.execute(() -> {
+            cr.insert(Favorites.CONTENT_URI, writer.getValues(mContext));
+
+            synchronized (mBgDataModel) {
+                checkItemInfoLocked(item.id, item, stackTrace);
+                mBgDataModel.addItem(mContext, item, true);
+                verifier.verifyModel();
             }
         });
     }
@@ -253,6 +265,7 @@
             }
             FileLog.d(TAG, "Finished deleting items");
         }
+        ModelVerifier verifier = new ModelVerifier();
 
         mWorkerExecutor.execute(() -> {
             for (ItemInfo item : items) {
@@ -260,6 +273,7 @@
                 mContext.getContentResolver().delete(uri, null, null);
 
                 mBgDataModel.removeItem(mContext, item);
+                verifier.verifyModel();
             }
         });
     }
@@ -273,6 +287,8 @@
             FileLog.d(TAG, "Deleting folder " + info, new Exception());
         }
 
+        ModelVerifier verifier = new ModelVerifier();
+
         mWorkerExecutor.execute(() -> {
             ContentResolver cr = mContext.getContentResolver();
             cr.delete(LauncherSettings.Favorites.CONTENT_URI,
@@ -282,6 +298,7 @@
 
             cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
             mBgDataModel.removeItem(mContext, info);
+            verifier.verifyModel();
         });
     }
 
@@ -336,6 +353,7 @@
 
     private abstract class UpdateItemBaseRunnable implements Runnable {
         private final StackTraceElement[] mStackTrace;
+        private final ModelVerifier mVerifier = new ModelVerifier();
 
         UpdateItemBaseRunnable() {
             mStackTrace = new Throwable().getStackTrace();
@@ -380,7 +398,45 @@
                 } else {
                     mBgDataModel.workspaceItems.remove(modelItem);
                 }
+                mVerifier.verifyModel();
             }
         }
     }
+
+    /**
+     * Utility class to verify model updates are propagated properly to the callback.
+     */
+    public class ModelVerifier {
+
+        final int startId;
+
+        ModelVerifier() {
+            startId = mBgDataModel.lastBindId;
+        }
+
+        void verifyModel() {
+            if (!mVerifyChanges || mModel.getCallback() == null) {
+                return;
+            }
+
+            int executeId = mBgDataModel.lastBindId;
+
+            mUiHandler.post(() -> {
+                int currentId = mBgDataModel.lastBindId;
+                if (currentId > executeId) {
+                    // Model was already bound after job was executed.
+                    return;
+                }
+                if (executeId == startId) {
+                    // Bound model has not changed during the job
+                    return;
+                }
+                // Bound model was changed between submitting the job and executing the job
+                Callbacks callbacks = mModel.getCallback();
+                if (callbacks != null) {
+                    callbacks.rebindModel();
+                }
+            });
+        }
+    }
 }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index b3ef7bb..e427a81 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -77,6 +77,7 @@
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Themes;
 
@@ -907,12 +908,9 @@
 
     @Override
     public boolean onLongClick(View v) {
+        if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
         // Return early if not the correct view
         if (!(v.getParent() instanceof DeepShortcutView)) return false;
-        // Return early if global dragging is not enabled
-        if (!mLauncher.isDraggingEnabled()) return false;
-        // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
-        if (mLauncher.getDragController().isDragging()) return false;
 
         // Long clicked on a shortcut.
         DeepShortcutView sv = (DeepShortcutView) v.getParent();
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 450a690..9ad266b 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -30,6 +30,7 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.touch.ItemClickHandler;
 
 /**
  * A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
@@ -120,7 +121,7 @@
         mBubbleText.setText(usingLongLabel ? longLabel : mDetail.getShortLabel());
 
         // TODO: Add the click handler to this view directly and not the child view.
-        mBubbleText.setOnClickListener(Launcher.getLauncher(getContext()));
+        mBubbleText.setOnClickListener(ItemClickHandler.INSTANCE);
         mBubbleText.setOnLongClickListener(container);
         mBubbleText.setOnTouchListener(container);
     }
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
new file mode 100644
index 0000000..8f83648
--- /dev/null
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -0,0 +1,139 @@
+/*
+ * 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.states;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.provider.Settings.System.ACCELEROMETER_ROTATION;
+import static android.provider.Settings.System.getUriFor;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.provider.Settings;
+
+import com.android.launcher3.R;
+
+/**
+ * Utility class to manage launcher rotation
+ */
+public class RotationHelper extends ContentObserver {
+
+    public static final int REQUEST_NONE = 0;
+    public static final int REQUEST_ROTATE = 1;
+    public static final int REQUEST_LOCK = 2;
+
+    private final Activity mActivity;
+    private final ContentResolver mCr;
+
+    private final boolean mIgnoreAutoRotateSettings;
+    private boolean mAutoRotateEnabled;
+
+    /**
+     * Rotation request made by {@link InternalStateHandler}. This supersedes any other request.
+     */
+    private int mStateHandlerRequest = REQUEST_NONE;
+    /**
+     * Rotation request made by a Launcher State
+     */
+    private int mCurrentStateRequest = REQUEST_NONE;
+
+    // This is used to defer setting rotation flags until the activity is being created
+    private boolean mInitialized;
+    public boolean mDestroyed;
+
+    private int mLastActivityFlags = -1;
+
+    public RotationHelper(Activity activity) {
+        super(new Handler());
+        mActivity = activity;
+
+        // On large devices we do not handle auto-rotate differently.
+        mIgnoreAutoRotateSettings = mActivity.getResources().getBoolean(R.bool.allow_rotation);
+        if (!mIgnoreAutoRotateSettings) {
+            mCr = mActivity.getContentResolver();
+            mCr.registerContentObserver(getUriFor(ACCELEROMETER_ROTATION), false, this);
+            mAutoRotateEnabled = Settings.System.getInt(mCr, ACCELEROMETER_ROTATION, 1) == 1;
+        } else {
+            mCr = null;
+        }
+    }
+
+    @Override
+    public void onChange(boolean selfChange) {
+        mAutoRotateEnabled = Settings.System.getInt(mCr, ACCELEROMETER_ROTATION, 1) == 1;
+        notifyChange();
+    }
+
+    public void setStateHandlerRequest(int request) {
+        if (mStateHandlerRequest != request) {
+            mStateHandlerRequest = request;
+            notifyChange();
+        }
+    }
+
+    public void setCurrentStateRequest(int request) {
+        if (mCurrentStateRequest != request) {
+            mCurrentStateRequest = request;
+            notifyChange();
+        }
+    }
+
+    public void initialize() {
+        if (!mInitialized) {
+            mInitialized = true;
+            notifyChange();
+        }
+    }
+
+    public void destroy() {
+        if (!mDestroyed) {
+            mDestroyed = true;
+            if (mCr != null) {
+                mCr.unregisterContentObserver(this);
+            }
+        }
+    }
+
+    private void notifyChange() {
+        if (!mInitialized || mDestroyed) {
+            return;
+        }
+
+        final int activityFlags;
+        if (mStateHandlerRequest != REQUEST_NONE) {
+            activityFlags = mStateHandlerRequest == REQUEST_LOCK ?
+                    SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
+        } else if (mCurrentStateRequest == REQUEST_LOCK) {
+            activityFlags = SCREEN_ORIENTATION_LOCKED;
+        } else if (mIgnoreAutoRotateSettings || mCurrentStateRequest == REQUEST_ROTATE) {
+            activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
+        } else if (mAutoRotateEnabled) {
+            // If auto rotation is on, lock to device orientation
+            activityFlags = SCREEN_ORIENTATION_NOSENSOR;
+        } else {
+            // If auto rotation is off, allow rotation on the activity, in case the user is using
+            // forced rotation.
+            activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
+        }
+        if (activityFlags != mLastActivityFlags) {
+            mLastActivityFlags = activityFlags;
+            mActivity.setRequestedOrientation(mLastActivityFlags);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 4f8456f..89a9e2d 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -16,10 +16,9 @@
 package com.android.launcher3.states;
 
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
+import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
 
-import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
-import android.os.Handler;
 import android.view.View;
 
 import com.android.launcher3.DeviceProfile;
@@ -38,10 +37,6 @@
             FLAG_DISABLE_ACCESSIBILITY | FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED |
             FLAG_DISABLE_PAGE_CLIPPING | FLAG_PAGE_BACKGROUNDS;
 
-    // Determines how long to wait after a rotation before restoring the screen orientation to
-    // match the sensor state.
-    private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500;
-
     public SpringLoadedState(int id) {
         super(id, ContainerType.OVERVIEW, SPRING_LOADED_TRANSITION_MS, STATE_FLAGS);
     }
@@ -84,30 +79,16 @@
         ws.showPageIndicatorAtCurrentScroll();
         ws.getPageIndicator().setShouldAutoHide(false);
 
-        // Lock the orientation:
-        if (launcher.isRotationEnabled()) {
-            launcher.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
-        }
-
         // Prevent any Un/InstallShortcutReceivers from updating the db while we are
         // in spring loaded mode
         InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_DRAG_AND_DROP);
+        launcher.getRotationHelper().setCurrentStateRequest(REQUEST_LOCK);
     }
 
     @Override
     public void onStateDisabled(final Launcher launcher) {
         launcher.getWorkspace().getPageIndicator().setShouldAutoHide(true);
 
-        // Unlock rotation lock
-        if (launcher.isRotationEnabled()) {
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    launcher.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
-                }
-            }, RESTORE_SCREEN_ORIENTATION_DELAY);
-        }
-
         // Re-enable any Un/InstallShortcutReceiver and now process any queued items
         InstallShortcutReceiver.disableAndFlushInstallQueue(
                 InstallShortcutReceiver.FLAG_DRAG_AND_DROP, launcher);
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
new file mode 100644
index 0000000..f2f5592
--- /dev/null
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.touch;
+
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_QUIET_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
+import static com.android.launcher3.Launcher.REQUEST_BIND_PENDING_APPWIDGET;
+import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET;
+
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.os.Process;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Toast;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.PromiseAppInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.widget.PendingAppWidgetHostView;
+import com.android.launcher3.widget.WidgetAddFlowHandler;
+
+/**
+ * Class for handling clicks on workspace and all-apps items
+ */
+public class ItemClickHandler {
+
+    /**
+     * Instance used for click handling on items
+     */
+    public static final OnClickListener INSTANCE = ItemClickHandler::onClick;
+
+    private static void onClick(View v) {
+        // Make sure that rogue clicks don't get through while allapps is launching, or after the
+        // view has detached (it's possible for this to happen if the view is removed mid touch).
+        if (v.getWindowToken() == null) {
+            return;
+        }
+
+        Launcher launcher = Launcher.getLauncher(v.getContext());
+        if (!launcher.getWorkspace().isFinishedSwitchingState()) {
+            return;
+        }
+
+        Object tag = v.getTag();
+        if (tag instanceof ShortcutInfo) {
+            onClickAppShortcut(v, (ShortcutInfo) tag, launcher);
+        } else if (tag instanceof FolderInfo) {
+            if (v instanceof FolderIcon) {
+                onClickFolderIcon(v);
+            }
+        } else if (tag instanceof AppInfo) {
+            startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);
+        } else if (tag instanceof LauncherAppWidgetInfo) {
+            if (v instanceof PendingAppWidgetHostView) {
+                onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
+            }
+        }
+    }
+
+    /**
+     * Event handler for a folder icon click.
+     *
+     * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
+     */
+    private static void onClickFolderIcon(View v) {
+        Folder folder = ((FolderIcon) v).getFolder();
+        if (!folder.isOpen() && !folder.isDestroyed()) {
+            // Open the requested folder
+            folder.animateOpen();
+        }
+    }
+
+    /**
+     * Event handler for the app widget view which has not fully restored.
+     */
+    private static void onClickPendingWidget(PendingAppWidgetHostView v, Launcher launcher) {
+        if (launcher.getPackageManager().isSafeMode()) {
+            Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
+            return;
+        }
+
+        final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
+        if (v.isReadyForClickSetup()) {
+            LauncherAppWidgetProviderInfo appWidgetInfo = AppWidgetManagerCompat
+                    .getInstance(launcher).findProvider(info.providerName, info.user);
+            if (appWidgetInfo == null) {
+                return;
+            }
+            WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo);
+
+            if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
+                if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
+                    // This should not happen, as we make sure that an Id is allocated during bind.
+                    return;
+                }
+                addFlowHandler.startBindFlow(launcher, info.appWidgetId, info,
+                        REQUEST_BIND_PENDING_APPWIDGET);
+            } else {
+                addFlowHandler.startConfigActivity(launcher, info, REQUEST_RECONFIGURE_APPWIDGET);
+            }
+        } else {
+            final String packageName = info.providerName.getPackageName();
+            onClickPendingAppItem(v, launcher, packageName, info.installProgress >= 0);
+        }
+    }
+
+    private static void onClickPendingAppItem(View v, Launcher launcher, String packageName,
+            boolean downloadStarted) {
+        if (downloadStarted) {
+            // If the download has started, simply direct to the market app.
+            startMarketIntentForPackage(v, launcher, packageName);
+            return;
+        }
+        new AlertDialog.Builder(launcher)
+                .setTitle(R.string.abandoned_promises_title)
+                .setMessage(R.string.abandoned_promise_explanation)
+                .setPositiveButton(R.string.abandoned_search,
+                        (d, i) -> startMarketIntentForPackage(v, launcher, packageName))
+                .setNeutralButton(R.string.abandoned_clean_this,
+                        (d, i) -> launcher.getWorkspace()
+                                .removeAbandonedPromise(packageName, Process.myUserHandle()))
+                .create().show();
+    }
+
+    private static void startMarketIntentForPackage(View v, Launcher launcher, String packageName) {
+        ItemInfo item = (ItemInfo) v.getTag();
+        Intent intent = new PackageManagerHelper(launcher).getMarketIntent(packageName);
+        launcher.startActivitySafely(v, intent, item);
+    }
+
+    /**
+     * Event handler for an app shortcut click.
+     *
+     * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
+     */
+    private static void onClickAppShortcut(View v, ShortcutInfo shortcut, Launcher launcher) {
+        if (shortcut.isDisabled()) {
+            final int disabledFlags = shortcut.runtimeStatusFlags & ShortcutInfo.FLAG_DISABLED_MASK;
+            if ((disabledFlags &
+                    ~FLAG_DISABLED_SUSPENDED &
+                    ~FLAG_DISABLED_QUIET_USER) == 0) {
+                // If the app is only disabled because of the above flags, launch activity anyway.
+                // Framework will tell the user why the app is suspended.
+            } else {
+                if (!TextUtils.isEmpty(shortcut.disabledMessage)) {
+                    // Use a message specific to this shortcut, if it has one.
+                    Toast.makeText(launcher, shortcut.disabledMessage, Toast.LENGTH_SHORT).show();
+                    return;
+                }
+                // Otherwise just use a generic error message.
+                int error = R.string.activity_not_available;
+                if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) {
+                    error = R.string.safemode_shortcut_error;
+                } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 ||
+                        (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) {
+                    error = R.string.shortcut_not_available;
+                }
+                Toast.makeText(launcher, error, Toast.LENGTH_SHORT).show();
+                return;
+            }
+        }
+
+        // Check for abandoned promise
+        if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()) {
+            String packageName = shortcut.intent.getComponent() != null ?
+                    shortcut.intent.getComponent().getPackageName() : shortcut.intent.getPackage();
+            if (!TextUtils.isEmpty(packageName)) {
+                onClickPendingAppItem(v, launcher, packageName,
+                        shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE));
+                return;
+            }
+        }
+
+        // Start activities
+        startAppShortcutOrInfoActivity(v, shortcut, launcher);
+    }
+
+    private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
+        Intent intent;
+        if (item instanceof PromiseAppInfo) {
+            PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
+            intent = promiseAppInfo.getMarketIntent(launcher);
+        } else {
+            intent = item.getIntent();
+        }
+        if (intent == null) {
+            throw new IllegalArgumentException("Input must have a valid intent");
+        }
+        if (item instanceof ShortcutInfo) {
+            ShortcutInfo si = (ShortcutInfo) item;
+            if (si.hasStatusFlag(ShortcutInfo.FLAG_SUPPORTS_WEB_UI)
+                    && intent.getAction() == Intent.ACTION_VIEW) {
+                // make a copy of the intent that has the package set to null
+                // we do this because the platform sometimes disables instant
+                // apps temporarily (triggered by the user) and fallbacks to the
+                // web ui. This only works though if the package isn't set
+                intent = new Intent(intent);
+                intent.setPackage(null);
+            }
+        }
+        launcher.startActivitySafely(v, intent, item);
+    }
+}
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
new file mode 100644
index 0000000..f10a695
--- /dev/null
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.touch;
+
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+
+import android.view.View;
+import android.view.View.OnLongClickListener;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.folder.Folder;
+
+/**
+ * Class to handle long-clicks on workspace items and start drag as a result.
+ */
+public class ItemLongClickListener {
+
+    public static OnLongClickListener INSTANCE_WORKSPACE =
+            ItemLongClickListener::onWorkspaceItemLongClick;
+
+    public static OnLongClickListener INSTANCE_ALL_APPS =
+            ItemLongClickListener::onAllAppsItemLongClick;
+
+    private static boolean onWorkspaceItemLongClick(View v) {
+        Launcher launcher = Launcher.getLauncher(v.getContext());
+        if (!canStartDrag(launcher)) return false;
+        if (!launcher.isInState(NORMAL) && !launcher.isInState(OVERVIEW)) return false;
+        if (!(v.getTag() instanceof ItemInfo)) return false;
+
+        launcher.setWaitingForResult(null);
+        beginDrag(v, launcher, (ItemInfo) v.getTag(), new DragOptions());
+        return true;
+    }
+
+    public static void beginDrag(View v, Launcher launcher, ItemInfo info,
+            DragOptions dragOptions) {
+        if (info.container >= 0) {
+            Folder folder = Folder.getOpen(launcher);
+            if (folder != null) {
+                if (!folder.getItemsInReadingOrder().contains(v)) {
+                    folder.close(true);
+                } else {
+                    folder.startDrag(v, dragOptions);
+                    return;
+                }
+            }
+        }
+
+        CellLayout.CellInfo longClickCellInfo = new CellLayout.CellInfo(v, info);
+        launcher.getWorkspace().startDrag(longClickCellInfo, dragOptions);
+    }
+
+    private static boolean onAllAppsItemLongClick(View v) {
+        Launcher launcher = Launcher.getLauncher(v.getContext());
+        if (!canStartDrag(launcher)) return false;
+        // When we have exited all apps or are in transition, disregard long clicks
+        if (!launcher.isInState(LauncherState.ALL_APPS) ||
+                launcher.getWorkspace().isSwitchingState()) return false;
+
+        // Start the drag
+        final DragController dragController = launcher.getDragController();
+        dragController.addDragListener(new DragController.DragListener() {
+            @Override
+            public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+                v.setVisibility(INVISIBLE);
+            }
+
+            @Override
+            public void onDragEnd() {
+                v.setVisibility(VISIBLE);
+                dragController.removeDragListener(this);
+            }
+        });
+
+        DeviceProfile grid = launcher.getDeviceProfile();
+        DragOptions options = new DragOptions();
+        options.intrinsicIconScaleFactor = (float) grid.allAppsIconSizePx / grid.iconSizePx;
+        launcher.getWorkspace().beginDragShared(v, launcher.getAppsView(), options);
+        return false;
+    }
+
+    public static boolean canStartDrag(Launcher launcher) {
+        if (launcher == null) {
+            return false;
+        }
+        // We prevent dragging when we are loading the workspace as it is possible to pick up a view
+        // that is subsequently removed from the workspace in startBinding().
+        if (launcher.isWorkspaceLocked()) return false;
+        // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
+        if (launcher.getDragController().isDragging()) return false;
+
+        return true;
+    }
+}
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index b80e94d..b793f54 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -177,7 +177,10 @@
             }
             int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX;
             int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY;
-            matrix[invert ? (m - cx - 1) : cx][cy] = i;
+            int x = invert ? (m - cx - 1) : cx;
+            if (x < m && cy < n) { // check if view fits into matrix, else skip
+                matrix[x][cy] = i;
+            }
         }
         if (DEBUG) {
             printMatrix(matrix);
diff --git a/src/com/android/launcher3/util/VerticalSwipeController.java b/src/com/android/launcher3/util/VerticalSwipeController.java
index a647378..ae5bfd5 100644
--- a/src/com/android/launcher3/util/VerticalSwipeController.java
+++ b/src/com/android/launcher3/util/VerticalSwipeController.java
@@ -18,12 +18,10 @@
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
-import android.support.animation.SpringAnimation;
 import android.util.Log;
 import android.view.MotionEvent;
 
@@ -31,13 +29,10 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsContainerView;
 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;
 
 /**
  * Handles vertical touch gesture on the DragLayer allowing transitioning from
@@ -68,8 +63,6 @@
     // Ratio of transition process [0, 1] to drag displacement (px)
     private float mProgressMultiplier;
 
-    protected SpringAnimationHandler[] mSpringHandlers;
-
     public VerticalSwipeController(Launcher l, LauncherState baseState) {
         this(l, baseState, ALL_APPS, SwipeDetector.VERTICAL);
     }
@@ -104,29 +97,6 @@
         }
     }
 
-    protected void initSprings() {
-        AllAppsContainerView appsView = mLauncher.getAppsView();
-
-        SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
-        if (handler == null) {
-            mSpringHandlers = new SpringAnimationHandler[0];
-            return;
-        }
-
-        ArrayList<SpringAnimationHandler> handlers = new ArrayList<>();
-        handlers.add(handler);
-
-        SpringAnimation searchSpring = appsView.getSearchUiManager().getSpringForFling();
-        if (searchSpring != null) {
-            SpringAnimationHandler searchHandler =
-                    new SpringAnimationHandler(Y_DIRECTION, handler.getFactory());
-            searchHandler.add(searchSpring, true /* setDefaultValues */);
-            handlers.add(searchHandler);
-        }
-
-        mSpringHandlers = handlers.toArray(new SpringAnimationHandler[handlers.size()]);
-    }
-
     @Override
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -155,10 +125,6 @@
 
             mDetector.setDetectableScrollConditions(
                     directionsToDetectScroll, ignoreSlopWhenSettling);
-
-            if (mSpringHandlers == null) {
-                initSprings();
-            }
         }
 
         if (mNoIntercept) {
@@ -173,9 +139,6 @@
 
     @Override
     public boolean onControllerTouchEvent(MotionEvent ev) {
-        for (SpringAnimationHandler h : mSpringHandlers) {
-            h.addMovement(ev);
-        }
         return mDetector.onTouchEvent(ev);
     }
 
@@ -198,10 +161,6 @@
             mCurrentAnimation.pause();
             mStartProgress = mCurrentAnimation.getProgressFraction();
         }
-
-        for (SpringAnimationHandler h : mSpringHandlers) {
-            h.skipToEnd();
-        }
     }
 
     protected boolean isTransitionFlipped() {
@@ -244,13 +203,6 @@
             }
         }
 
-        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 */);
-            }
-        }
-
         mCurrentAnimation.setEndAction(() -> {
             mLauncher.getStateManager().goToState(targetState, false);
             onTransitionComplete(fling, targetState == mToState);
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 58c9148..1cd6699 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -22,6 +22,8 @@
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.util.Property;
@@ -43,6 +45,7 @@
 public class RecyclerViewFastScroller extends View {
 
     private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
+    private static final Rect sTempRect = new Rect();
 
     private static final Property<RecyclerViewFastScroller, Integer> TRACK_WIDTH =
             new Property<RecyclerViewFastScroller, Integer>(Integer.class, "width") {
@@ -204,9 +207,9 @@
      * Handles the touch event and determines whether to show the fast scroller (or updates it if
      * it is already showing).
      */
-    public boolean handleTouchEvent(MotionEvent ev) {
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
+    public boolean handleTouchEvent(MotionEvent ev, Point offset) {
+        int x = (int) ev.getX() - offset.x;
+        int y = (int) ev.getY() - offset.y;
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN:
                 // Keep track of the down positions
@@ -260,7 +263,6 @@
     }
 
     private void calcTouchOffsetAndPrepToFastScroll(int downY, int lastY) {
-        mRv.getParent().requestDisallowInterceptTouchEvent(true);
         mIsDragging = true;
         if (mCanThumbDetach) {
             mIsThumbDetached = true;
@@ -358,4 +360,16 @@
                 mMaxWidth, mRv.getScrollbarTrackHeight() - mMaxWidth - height);
         mPopupView.setTranslationY(top);
     }
+
+    public boolean isHitInParent(float x, float y, Point outOffset) {
+        if (mThumbOffsetY < 0) {
+            return false;
+        }
+        getHitRect(sTempRect);
+        sTempRect.top += mRv.getScrollBarTop();
+        if (outOffset != null) {
+            outOffset.set(sTempRect.left, sTempRect.top);
+        }
+        return sTempRect.contains((int) x, (int) y);
+    }
 }
diff --git a/src/com/android/launcher3/views/SpringRelativeLayout.java b/src/com/android/launcher3/views/SpringRelativeLayout.java
new file mode 100644
index 0000000..090b3e6
--- /dev/null
+++ b/src/com/android/launcher3/views/SpringRelativeLayout.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.views;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.support.animation.FloatPropertyCompat;
+import android.support.animation.SpringAnimation;
+import android.support.animation.SpringForce;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.EdgeEffectFactory;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.View;
+import android.widget.EdgeEffect;
+import android.widget.RelativeLayout;
+
+import static android.support.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
+import static android.support.animation.SpringForce.STIFFNESS_LOW;
+import static android.support.animation.SpringForce.STIFFNESS_MEDIUM;
+
+public class SpringRelativeLayout extends RelativeLayout {
+
+    private static final float STIFFNESS = (STIFFNESS_MEDIUM + STIFFNESS_LOW) / 2;
+    private static final float DAMPING_RATIO = DAMPING_RATIO_MEDIUM_BOUNCY;
+    private static final float VELOCITY_MULTIPLIER = 0.3f;
+
+    private static final FloatPropertyCompat<SpringRelativeLayout> DAMPED_SCROLL =
+            new FloatPropertyCompat<SpringRelativeLayout>("value") {
+
+                @Override
+                public float getValue(SpringRelativeLayout object) {
+                    return object.mDampedScrollShift;
+                }
+
+                @Override
+                public void setValue(SpringRelativeLayout object, float value) {
+                    object.setDampedScrollShift(value);
+                }
+            };
+
+    private final SparseBooleanArray mSpringViews = new SparseBooleanArray();
+    private final SpringAnimation mSpring;
+
+    private float mDampedScrollShift = 0;
+
+    public SpringRelativeLayout(Context context) {
+        this(context, null);
+    }
+
+    public SpringRelativeLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SpringRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mSpring = new SpringAnimation(this, DAMPED_SCROLL, 0);
+        mSpring.setSpring(new SpringForce(0)
+                .setStiffness(STIFFNESS)
+                .setDampingRatio(DAMPING_RATIO));
+    }
+
+    public void addSpringView(int id) {
+        mSpringViews.put(id, true);
+    }
+
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        if (mDampedScrollShift != 0 && mSpringViews.get(child.getId())) {
+            canvas.translate(0, mDampedScrollShift);
+            boolean result = super.drawChild(canvas, child, drawingTime);
+            canvas.translate(0, -mDampedScrollShift);
+            return result;
+        }
+        return super.drawChild(canvas, child, drawingTime);
+    }
+
+    private void setDampedScrollShift(float shift) {
+        if (shift != mDampedScrollShift) {
+            mDampedScrollShift = shift;
+            invalidate();
+        }
+    }
+
+    private void finishScrollWithVelocity(float velocity) {
+        mSpring.setStartVelocity(velocity);
+        mSpring.setStartValue(mDampedScrollShift);
+        mSpring.start();
+    }
+
+    public EdgeEffectFactory createEdgeEffectFactory() {
+        return new SpringEdgeEffectFactory();
+    }
+
+    private class SpringEdgeEffectFactory extends EdgeEffectFactory {
+
+        @NonNull @Override
+        protected EdgeEffect createEdgeEffect(RecyclerView view, int direction) {
+            switch (direction) {
+                case DIRECTION_TOP:
+                    return new SpringEdgeEffect(getContext(), +VELOCITY_MULTIPLIER);
+                case DIRECTION_BOTTOM:
+                    return new SpringEdgeEffect(getContext(), -VELOCITY_MULTIPLIER);
+            }
+            return super.createEdgeEffect(view, direction);
+        }
+    }
+
+    private class SpringEdgeEffect extends EdgeEffect {
+
+        private final float mVelocityMultiplier;
+
+        private float mDistance;
+
+        public SpringEdgeEffect(Context context, float velocityMultiplier) {
+            super(context);
+            mVelocityMultiplier = velocityMultiplier;
+        }
+
+        @Override
+        public boolean draw(Canvas canvas) {
+            return false;
+        }
+
+        @Override
+        public void onAbsorb(int velocity) {
+            finishScrollWithVelocity(velocity * mVelocityMultiplier);
+        }
+
+        @Override
+        public void onPull(float deltaDistance, float displacement) {
+            mDistance += deltaDistance * (mVelocityMultiplier / 3f);
+            setDampedScrollShift(mDistance * getHeight());
+        }
+
+        @Override
+        public void onRelease() {
+            mDistance = 0;
+            finishScrollWithVelocity(0);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/views/TopRoundedCornerView.java b/src/com/android/launcher3/views/TopRoundedCornerView.java
index ba223c4..3ba8ca3 100644
--- a/src/com/android/launcher3/views/TopRoundedCornerView.java
+++ b/src/com/android/launcher3/views/TopRoundedCornerView.java
@@ -17,12 +17,14 @@
 
 import android.content.Context;
 import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.RectF;
 import android.util.AttributeSet;
 import android.widget.FrameLayout;
 
 import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
 
 /**
  * View with top rounded corners.
@@ -33,23 +35,41 @@
     private final Path mClipPath = new Path();
     private float[] mRadii;
 
+    private final Paint mNavBarScrimPaint;
+    private int mNavBarScrimHeight = 0;
+
     public TopRoundedCornerView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
         int radius = getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
         mRadii = new float[] {radius, radius, radius, radius, 0, 0, 0, 0};
+
+        mNavBarScrimPaint = new Paint();
+        mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
     }
 
     public TopRoundedCornerView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
+    public void setNavBarScrimHeight(int height) {
+        if (mNavBarScrimHeight != height) {
+            mNavBarScrimHeight = height;
+            invalidate();
+        }
+    }
+
     @Override
     public void draw(Canvas canvas) {
         canvas.save();
         canvas.clipPath(mClipPath);
         super.draw(canvas);
         canvas.restore();
+
+        if (mNavBarScrimHeight > 0) {
+            canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
+                    mNavBarScrimPaint);
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index c51842d..10708d6 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -32,6 +32,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.graphics.ColorScrim;
+import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.SystemUiController;
@@ -71,7 +72,7 @@
 
     @Override
     public final boolean onLongClick(View v) {
-        if (!mLauncher.isDraggingEnabled()) return false;
+        if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
 
         if (v instanceof WidgetCell) {
             return beginDraggingWidget((WidgetCell) v);
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 6970833..961799d 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -43,6 +43,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
@@ -83,7 +84,7 @@
 
         setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
         updateAppWidget(null);
-        setOnClickListener(mLauncher);
+        setOnClickListener(ItemClickHandler.INSTANCE);
 
         if (info.pendingItemInfo == null) {
             info.pendingItemInfo = new PackageItemInfo(info.providerName.getPackageName());
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index 48f8afe..b31feed 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -23,7 +23,6 @@
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
-import android.view.View;
 import android.view.animation.AnimationUtils;
 
 import com.android.launcher3.Insettable;
@@ -31,6 +30,8 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.R;
+import com.android.launcher3.views.RecyclerViewFastScroller;
+import com.android.launcher3.views.TopRoundedCornerView;
 
 /**
  * Popup for showing the full list of available widgets
@@ -46,7 +47,6 @@
 
     private final WidgetsListAdapter mAdapter;
 
-    private View mNavBarScrim;
     private WidgetsRecyclerView mRecyclerView;
 
     public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -65,7 +65,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mContent = findViewById(R.id.container);
-        mNavBarScrim = findViewById(R.id.nav_bar_bg);
 
         mRecyclerView = findViewById(R.id.widgets_list_view);
         mRecyclerView.setAdapter(mAdapter);
@@ -91,7 +90,6 @@
     public void setInsets(Rect insets) {
         mInsets.set(insets);
 
-        mNavBarScrim.getLayoutParams().height = insets.bottom;
         mRecyclerView.setPadding(
                 mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(),
                 mRecyclerView.getPaddingRight(), insets.bottom);
@@ -100,6 +98,8 @@
         } else {
             clearNavBarColor();
         }
+
+        ((TopRoundedCornerView) mContent).setNavBarScrimHeight(mInsets.bottom);
         requestLayout();
     }
 
@@ -195,7 +195,11 @@
         // Disable swipe down when recycler view is scrolling
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mNoIntercept = false;
-            if (mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
+            RecyclerViewFastScroller scroller = mRecyclerView.getScrollbar();
+            if (scroller.getThumbOffsetY() >= 0 &&
+                    mLauncher.getDragLayer().isEventOverView(scroller, ev)) {
+                mNoIntercept = true;
+            } else if (mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
                 mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer());
             }
         }
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 89c88a4..124058e 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -17,8 +17,12 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
+import android.graphics.Point;
 import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 
 import com.android.launcher3.BaseRecyclerView;
@@ -27,13 +31,15 @@
 /**
  * The widgets recycler view.
  */
-public class WidgetsRecyclerView extends BaseRecyclerView {
+public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouchListener {
 
-    private static final String TAG = "WidgetsRecyclerView";
     private WidgetsListAdapter mAdapter;
 
     private final int mScrollbarTop;
 
+    private final Point mFastScrollerOffset = new Point();
+    private boolean mTouchDownOnScroller;
+
     public WidgetsRecyclerView(Context context) {
         this(context, null);
     }
@@ -46,6 +52,7 @@
         // API 21 and below only support 3 parameter ctor.
         super(context, attrs, defStyleAttr);
         mScrollbarTop = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
+        addOnItemTouchListener(this);
     }
 
     public WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
@@ -56,7 +63,6 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        addOnItemTouchListener(this);
         // create a layout manager with Launcher's context so that scroll position
         // can be preserved during screen rotation.
         setLayoutManager(new LinearLayoutManager(getContext()));
@@ -145,4 +151,26 @@
     public int getScrollBarTop() {
         return mScrollbarTop;
     }
+
+    @Override
+    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+        if (e.getAction() == MotionEvent.ACTION_DOWN) {
+            mTouchDownOnScroller =
+                    mScrollbar.isHitInParent(e.getX(), e.getY(), mFastScrollerOffset);
+        }
+        if (mTouchDownOnScroller) {
+            return mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
+        }
+        return false;
+    }
+
+    @Override
+    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+        if (mTouchDownOnScroller) {
+            mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
+        }
+    }
+
+    @Override
+    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { }
 }
\ No newline at end of file
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
index 4168e11..616e25c 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.uioverrides;
 
+import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.WorkspaceStateTransitionAnimation.NO_ANIM_PROPERTY_SETTER;
 
 import android.content.Context;
@@ -174,7 +175,8 @@
     }
 
     private void setState(LauncherState state, PropertySetter setter) {
-        setter.setViewAlpha(this, 1f - state.getHoseatAlpha(mLauncher), Interpolators.ACCEL);
+        float myAlpha = state == OVERVIEW ? 1 : 0;
+        setter.setViewAlpha(this, myAlpha, Interpolators.ACCEL);
     }
 
     public static int getButtonBarHeight(Launcher launcher) {
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index a16ae48..de75ac9 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -16,25 +16,17 @@
 
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.PointF;
-import android.os.Bundle;
-import android.view.View;
 import android.view.View.AccessibilityDelegate;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.graphics.BitmapRenderer;
 import com.android.launcher3.util.TouchController;
 
 public class UiFactory {
 
-    public static final boolean USE_HARDWARE_BITMAP = false;
-
     public static TouchController[] createTouchControllers(Launcher launcher) {
         return new TouchController[] {
                 new AllAppsSwipeController(launcher), new PinchToOverviewListener(launcher)};
@@ -54,13 +46,6 @@
         launcher.getStateManager().goToState(OVERVIEW);
     }
 
-    public static Bitmap createFromRenderer(int width, int height, boolean forceSoftwareRenderer,
-            BitmapRenderer renderer) {
-        Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        renderer.render(new Canvas(result));
-        return result;
-    }
-
     public static void resetOverview(Launcher launcher) { }
 
     public static void onLauncherStateOrFocusChanged(Launcher launcher) { }
diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index cf90afd..b217847 100644
--- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -1,5 +1,11 @@
 package com.android.launcher3.model;
 
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -24,8 +30,8 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.LauncherProvider;
 import com.android.launcher3.graphics.BitmapInfo;
 import com.android.launcher3.util.ComponentKey;
@@ -43,12 +49,6 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 /**
  * Base class for writing tests for Model update tasks.
  */
@@ -82,7 +82,7 @@
         modelWriter = mock(ModelWriter.class);
 
         when(appState.getModel()).thenReturn(model);
-        when(model.getWriter(anyBoolean())).thenReturn(modelWriter);
+        when(model.getWriter(anyBoolean(), anyBoolean())).thenReturn(modelWriter);
         when(model.getCallback()).thenReturn(callbacks);
 
         myUser = Process.myUserHandle();
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 9e55127..011aa22 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -15,9 +15,6 @@
  */
 package com.android.launcher3.ui;
 
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
 import android.app.Instrumentation;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -37,22 +34,27 @@
 import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.Until;
 import android.view.MotionEvent;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.testcomponent.AppWidgetNoConfig;
 import com.android.launcher3.testcomponent.AppWidgetWithConfig;
+
+import org.junit.Before;
+
 import java.util.Locale;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import org.junit.Before;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 
 /**
  * Base class for all instrumentation tests providing various utility methods.
@@ -63,6 +65,7 @@
     public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
 
     public static final long DEFAULT_UI_TIMEOUT = 3000;
+    public static final long LARGE_UI_TIMEOUT = 10000;
     public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5;
 
     protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
@@ -78,11 +81,6 @@
     }
 
     protected void lockRotation(boolean naturalOrientation) throws RemoteException {
-        Utilities.getPrefs(mTargetContext)
-                .edit()
-                .putBoolean(Utilities.ALLOW_ROTATION_PREFERENCE_KEY, !naturalOrientation)
-                .commit();
-
         if (naturalOrientation) {
             mDevice.setOrientationNatural();
         } else {
@@ -100,8 +98,13 @@
     protected UiObject2 openAllApps() {
         mDevice.waitForIdle();
         if (FeatureFlags.NO_ALL_APPS_ICON) {
-            // clicking on the page indicator brings up all apps tray on non tablets.
-            findViewById(R.id.page_indicator).click();
+            UiObject2 hotseat = mDevice.wait(
+                    Until.findObject(getSelectorForId(R.id.hotseat)), 2500);
+            Point start = hotseat.getVisibleCenter();
+            int endY = (int) (mDevice.getDisplayHeight() * 0.1f);
+            // 100 px/step
+            mDevice.swipe(start.x, start.y, start.x, endY, (start.y - endY) / 100);
+
         } else {
             mDevice.wait(Until.findObject(
                     By.desc(mTargetContext.getString(R.string.all_apps_button_label))),
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
new file mode 100644
index 0000000..ccee7da
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 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.ui;
+
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Condition;
+import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.rule.LauncherActivityRule;
+import com.android.launcher3.util.rule.ShellCommandRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertTrue;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class WorkTabTest extends AbstractLauncherUiTest {
+    @Rule
+    public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
+    @Rule
+    public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
+
+    private int mProfileUserId;
+
+    @Before
+    public void createWorkProfile() throws Exception {
+        String output =
+                mDevice.executeShellCommand(
+                        "pm create-user --profileOf 0 --managed TestProfile");
+        assertTrue("Failed to create work profile", output.startsWith("Success"));
+
+        String[] tokens = output.split("\\s+");
+        mProfileUserId = Integer.parseInt(tokens[tokens.length - 1]);
+
+        mDevice.executeShellCommand("am start-user " + mProfileUserId);
+    }
+
+    @After
+    public void removeWorkProfile() throws Exception {
+        mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
+    }
+
+    @Test
+    public void workTabExists() {
+        mActivityMonitor.startLauncher();
+
+        // Open all apps and wait for load complete
+        final UiObject2 appsContainer = openAllApps();
+        assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
+
+        assertTrue("Personal tab is missing",
+                mDevice.wait(Until.hasObject(getSelectorForId(R.id.tab_personal)),
+                        LARGE_UI_TIMEOUT));
+        assertTrue("Work tab is missing",
+                mDevice.wait(Until.hasObject(getSelectorForId(R.id.tab_work)), LARGE_UI_TIMEOUT));
+    }
+}
\ No newline at end of file