Merge "Add new FolderLabelStates to better understand smart folder acceptance rate based on primary suggestion." into ub-launcher3-master
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index 5abeae4..85741ed 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -46,11 +46,8 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.model.PagedViewOrientedState;
-import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.touch.PortraitPagedViewHandler;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
@@ -59,6 +56,7 @@
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
+import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -127,7 +125,7 @@
     protected boolean mCanceled;
     protected int mFinishingRecentsAnimationForNewTaskId = -1;
 
-    private PagedViewOrientedState mOrientedState;
+    private RecentsOrientedState mOrientedState;
 
     protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
             GestureState gestureState, InputConsumerController inputConsumer) {
@@ -350,9 +348,11 @@
             // TODO(b/150300347): The first recents animation after launcher is started with the
             //  foreground app not in landscape will look funky until that bug is fixed
             displayRotation = mOrientedState.getDisplayRotation();
+
+            RectF tempRectF = new RectF(TEMP_RECT);
+            mOrientedState.mapRectFromNormalOrientation(tempRectF, dp.widthPx, dp.heightPx);
+            tempRectF.roundOut(TEMP_RECT);
         }
-        RotationHelper.getTargetRectForRotation(TEMP_RECT, dp.widthPx, dp.heightPx,
-            displayRotation);
         mAppWindowAnimationHelper.updateTargetRect(TEMP_RECT);
         if (mDeviceState.isFullyGesturalNavMode()) {
             // We can drag all the way to the top of the screen.
@@ -447,7 +447,7 @@
 
     protected PagedOrientationHandler getOrientationHandler() {
         if (mOrientedState == null) {
-            return new PortraitPagedViewHandler();
+            return PagedOrientationHandler.PORTRAIT;
         }
         return mOrientedState.getOrientationHandler();
     }
@@ -468,8 +468,8 @@
                     .setTargetSet(mRecentsAnimationTargets)
                     .setLauncherOnTop(false)));
         if (isFloatingIconView) {
-            RotationHelper.mapInverseRectFromNormalOrientation(startRect,
-                mDp.widthPx, mDp.heightPx, mOrientedState.getDisplayRotation());
+            mOrientedState.mapInverseRectFromNormalOrientation(
+                    startRect, mDp.widthPx, mDp.heightPx);
         }
         RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext);
         if (isFloatingIconView) {
@@ -500,8 +500,8 @@
 
                 rotatedRect.set(currentRect);
                 if (isFloatingIconView) {
-                    RotationHelper.mapRectFromNormalOrientation(rotatedRect,
-                        mDp.widthPx, mDp.heightPx, mOrientedState.getDisplayRotation());
+                    mOrientedState.mapRectFromNormalOrientation(
+                            rotatedRect, mDp.widthPx, mDp.heightPx);
                     mTransformParams.setCornerRadius(endRadius * progress + startRadius
                         * (1f - progress));
                 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
index 4abb86a..2214dd0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
@@ -33,8 +33,6 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.touch.PortraitPagedViewHandler;
-import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.util.ActivityInitListener;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index da73bc0..46b026e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -40,9 +40,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.touch.LandscapePagedViewHandler;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.touch.PortraitPagedViewHandler;
 import com.android.launcher3.util.ObjectWrapper;
 import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
 import com.android.quickstep.GestureState.GestureEndTarget;
@@ -503,8 +501,8 @@
                 PagedOrientationHandler orientationHandler = mRecentsView != null
                         ? mRecentsView.getPagedOrientationHandler()
                         : (mDp.isLandscape
-                                ? new LandscapePagedViewHandler()
-                                : new PortraitPagedViewHandler());
+                                ? PagedOrientationHandler.LANDSCAPE
+                                : PagedOrientationHandler.PORTRAIT);
                 return HomeAnimationFactory
                     .getDefaultWindowTargetRect(orientationHandler, mDp);
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
index d9e9cc7..75a5976 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
@@ -15,6 +15,13 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
+import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
+
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
@@ -33,7 +40,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.model.PagedViewOrientedState;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.RemoteAnimationTargets;
 import com.android.quickstep.SystemUiProxy;
@@ -47,13 +53,6 @@
 import com.android.systemui.shared.system.TransactionCompat;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
-import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
-
 /**
  * Utility class to handle window clip animation
  */
@@ -86,7 +85,7 @@
     private final Rect mTmpRect = new Rect();
     private final RectF mTmpRectF = new RectF();
     private final RectF mCurrentRectWithInsets = new RectF();
-    private PagedViewOrientedState mOrientedState;
+    private RecentsOrientedState mOrientedState;
     // Corner radius of windows, in pixels
     private final float mWindowCornerRadius;
     // Corner radius of windows when they're in overview mode.
@@ -105,7 +104,7 @@
     private TargetAlphaProvider mTaskAlphaCallback = (t, a) -> a;
     private TargetAlphaProvider mBaseAlphaCallback = (t, a) -> 1;
 
-    public AppWindowAnimationHelper(PagedViewOrientedState orientedState, Context context) {
+    public AppWindowAnimationHelper(RecentsOrientedState orientedState, Context context) {
         Resources res = context.getResources();
         mOrientedState = orientedState;
         mWindowCornerRadius = getWindowCornerRadius(res);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
index 763f5be..e455939 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
@@ -78,21 +78,6 @@
         }
     }
 
-    public void onLayoutChanged() {
-        if (mParent == null) {
-            return;
-        }
-        setRotation(mParent.getPagedOrientationHandler().getDegreesRotated());
-    }
-
-    public void setRtl(boolean rtl) {
-        if (mIsRtl == rtl) {
-            return;
-        }
-        mIsRtl = rtl;
-        invalidate();
-    }
-
     public void setVisibilityAlpha(float alpha) {
         if (mVisibilityAlpha != alpha) {
             mVisibilityAlpha = alpha;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index e62de18..98eb29a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -42,7 +42,6 @@
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.StateListener;
-import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.appprediction.PredictionUiStateManager;
 import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
@@ -172,7 +171,7 @@
                 mActivity.getAllAppsController(), ALL_APPS_PROGRESS, allAppsProgressOffscreen));
 
         ObjectAnimator dragHandleAnim = ObjectAnimator.ofInt(
-                mActivity.findViewById(R.id.scrim_view), ScrimView.DRAG_HANDLE_ALPHA, 0);
+                mActivity.getScrimView(), ScrimView.DRAG_HANDLE_ALPHA, 0);
         dragHandleAnim.setInterpolator(Interpolators.ACCEL_2);
         anim.play(dragHandleAnim);
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index e3b3102..5e8c232 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -100,8 +100,8 @@
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
@@ -122,6 +122,7 @@
 import com.android.quickstep.ViewUtils;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.RecentsOrientedState;
 import com.android.systemui.plugins.ResourceProvider;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.shared.recents.model.Task;
@@ -171,6 +172,8 @@
                 }
             };
 
+    protected final RecentsOrientedState mOrientationState = new RecentsOrientedState();
+
     private OrientationEventListener mOrientationListener;
     private int mPreviousRotation;
     protected RecentsAnimationController mRecentsAnimationController;
@@ -383,7 +386,7 @@
         mOrientationListener = new OrientationEventListener(getContext()) {
             @Override
             public void onOrientationChanged(int i) {
-                int rotation = RotationHelper.getRotationFromDegrees(i);
+                int rotation = RecentsOrientedState.getRotationForUserDegreesRotated(i);
                 if (mPreviousRotation != rotation) {
                     animateRecentsRotationInPlace(rotation);
                     if (rotation == 0) {
@@ -661,7 +664,7 @@
             final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex;
             final Task task = tasks.get(i);
             final TaskView taskView = (TaskView) getChildAt(pageIndex);
-            taskView.bind(task, mLayoutRotation);
+            taskView.bind(task, mOrientationState);
         }
 
         if (mNextPage == INVALID_PAGE) {
@@ -973,7 +976,7 @@
 
         AnimatorSet pa = setRecentsChangedOrientation(true);
         pa.addListener(AnimationSuccessListener.forRunnable(() -> {
-            updateLayoutRotation(newRotation);
+            setLayoutRotation(newRotation, mOrientationState.getDisplayRotation());
             mActivity.getDragLayer().recreateControllers();
             rotateAllChildTasks();
             setRecentsChangedOrientation(false).start();
@@ -999,8 +1002,7 @@
 
     private void rotateAllChildTasks() {
         for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView taskView = getTaskViewAt(i);
-            taskView.setOverviewRotation(mLayoutRotation);
+            getTaskViewAt(i).setOrientationState(mOrientationState);
         }
     }
 
@@ -1041,7 +1043,7 @@
                     new ComponentName(getContext(), getClass()), 0, 0), null, null, "", "", 0, 0,
                     false, true, false, false, new ActivityManager.TaskDescription(), 0,
                     new ComponentName("", ""), false);
-            taskView.bind(mTmpRunningTask, mLayoutRotation);
+            taskView.bind(mTmpRunningTask, mOrientationState);
         }
 
         boolean runningTaskTileHidden = mRunningTaskTileHidden;
@@ -1530,16 +1532,28 @@
         }
     }
 
-    @Override
     public void setLayoutRotation(int touchRotation, int displayRotation) {
-        if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
-            return;
+        if (mOrientationState.update(touchRotation, displayRotation)) {
+            mOrientationHandler = mOrientationState.getOrientationHandler();
+            mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
+            setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
+            mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated());
+            requestLayout();
         }
+    }
 
-        super.setLayoutRotation(touchRotation, displayRotation);
-        mClearAllButton.onLayoutChanged();
-        mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
-        setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
+    public void disableMultipleLayoutRotations(boolean disable) {
+        mOrientationState.disableMultipleOrientations(disable);
+        mOrientationHandler = mOrientationState.getOrientationHandler();
+        requestLayout();
+    }
+
+    public RecentsOrientedState getPagedViewOrientedState() {
+        return mOrientationState;
+    }
+
+    public PagedOrientationHandler getPagedOrientationHandler() {
+        return mOrientationHandler;
     }
 
     @Override
@@ -1986,8 +2000,7 @@
     public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
         float degreesRotated;
         if (navbarRotation == 0) {
-            degreesRotated = mOrientationState.areMultipleLayoutOrientationsDisabled() ? 0 :
-                    RotationHelper.getDegreesFromRotation(mLayoutRotation);
+            degreesRotated = mOrientationState.getTouchRotationDegrees();
         } else {
             degreesRotated = -navbarRotation;
         }
@@ -2001,14 +2014,14 @@
         return e -> {
             if (navbarRotation != 0
                     && !mOrientationState.areMultipleLayoutOrientationsDisabled()) {
-                RotationHelper.transformEventForNavBar(e, true);
+                mOrientationState.flipVertical(e);
                 super.onTouchEvent(e);
-                RotationHelper.transformEventForNavBar(e, false);
+                mOrientationState.flipVertical(e);
                 return;
             }
-            RotationHelper.transformEvent(-degreesRotated, e, true);
+            mOrientationState.transformEvent(-degreesRotated, e, true);
             super.onTouchEvent(e);
-            RotationHelper.transformEvent(-degreesRotated, e, false);
+            mOrientationState.transformEvent(-degreesRotated, e, false);
         };
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 7010f9a..ceb099f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -64,7 +64,6 @@
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
@@ -79,6 +78,7 @@
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.TaskCornerRadius;
 import com.android.quickstep.views.RecentsView.PageCallbacks;
 import com.android.quickstep.views.RecentsView.ScrollState;
@@ -119,19 +119,6 @@
     private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
             Collections.singletonList(new Rect());
 
-    public static final FloatProperty<TaskView> FULLSCREEN_PROGRESS =
-            new FloatProperty<TaskView>("fullscreenProgress") {
-                @Override
-                public void setValue(TaskView taskView, float v) {
-                    taskView.setFullscreenProgress(v);
-                }
-
-                @Override
-                public Float get(TaskView taskView) {
-                    return taskView.mFullscreenProgress;
-                }
-            };
-
     private static final FloatProperty<TaskView> FOCUS_TRANSITION =
             new FloatProperty<TaskView>("focusTransition") {
                 @Override
@@ -286,11 +273,11 @@
     /**
      * Updates this task view to the given {@param task}.
      */
-    public void bind(Task task, int recentsRotation) {
+    public void bind(Task task, RecentsOrientedState orientedState) {
         cancelPendingLoadTasks();
         mTask = task;
         mSnapshotView.bind(task);
-        setOverviewRotation(recentsRotation);
+        setOrientationState(orientedState);
     }
 
     public Task getTask() {
@@ -459,14 +446,15 @@
         }
     }
 
-    void setOverviewRotation(int iconRotation) {
-        PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
+    public void setOrientationState(RecentsOrientedState orientationState) {
+        int iconRotation = orientationState.getTouchRotation();
+        PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
         boolean isRtl = orientationHandler.getRecentsRtlSetting(getResources());
         LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
         snapshotParams.bottomMargin = LayoutUtils.thumbnailBottomMargin(getContext());
         int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
         LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
-        int rotation = RotationHelper.getDegreesFromRotation(iconRotation);
+        int rotation = orientationState.getTouchRotationDegrees();
         switch (iconRotation) {
             case Surface.ROTATION_90:
                 iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 8d42c4a..dcc85d5 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -66,9 +66,6 @@
        docked_stack_divider_thickness - 2 * docked_stack_divider_insets -->
     <dimen name="multi_window_task_divider_size">10dp</dimen>
 
-    <!-- same as vertical_drag_handle_size -->
-    <dimen name="shelf_surface_offset">24dp</dimen>
-
     <!-- Assistant Gestures -->
     <!-- Distance from the vertical edges of the screen in which assist gestures are recognized -->
     <dimen name="gestures_assistant_width">48dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java b/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
deleted file mode 100644
index c7cce0b..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.util.Size;
-import android.view.View;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.graphics.LauncherPreviewRenderer;
-import com.android.systemui.shared.system.SurfaceViewRequestReceiver;
-
-/** Render preview using surface view. */
-public class PreviewSurfaceRenderer {
-
-    /** Handle a received surface view request. */
-    public static void render(Context context, Bundle bundle) {
-        String gridName = bundle.getString("name");
-        bundle.remove("name");
-        if (gridName == null) {
-            gridName = InvariantDeviceProfile.getCurrentGridName(context);
-        }
-        final InvariantDeviceProfile idp = new InvariantDeviceProfile(context, gridName);
-
-        MAIN_EXECUTOR.execute(() -> {
-            View view = new LauncherPreviewRenderer(context, idp).getRenderedView();
-            new SurfaceViewRequestReceiver().onReceive(context, bundle, view,
-                    new Size(view.getMeasuredWidth(), view.getMeasuredHeight()));
-        });
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 3e73f49..95cd4f2 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -22,6 +22,8 @@
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
+import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
+
 import android.content.res.Resources;
 import android.graphics.Matrix;
 import android.graphics.Point;
@@ -33,8 +35,8 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
-import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.util.RecentsOrientedState.SurfaceRotation;
 
 import java.io.PrintWriter;
 
@@ -52,6 +54,9 @@
     private static final boolean DEBUG = false;
     private static final int MAX_ORIENTATIONS = 4;
 
+    private final Matrix mTmpMatrix = new Matrix();
+    private final float[] mTmpPoint = new float[2];
+
     private SparseArray<OrientationRectF> mSwipeTouchRegions = new SparseArray<>(MAX_ORIENTATIONS);
     private final RectF mAssistantLeftRegion = new RectF();
     private final RectF mAssistantRightRegion = new RectF();
@@ -304,42 +309,46 @@
         }
 
         boolean applyTransform(MotionEvent event, boolean forceTransform) {
-            // TODO(b/149658423): See if we can use RotationHelper.getRotationMatrix here
-            MotionEvent tmp = MotionEvent.obtain(event);
-            Matrix outMatrix = new Matrix();
-            int delta = RotationHelper.deltaRotation(mCurrentRotation, mRotation);
-            switch (delta) {
-                case Surface.ROTATION_0:
-                    outMatrix.reset();
-                    break;
-                case Surface.ROTATION_90:
-                    outMatrix.setRotate(270);
-                    outMatrix.postTranslate(0, mHeight);
-                    break;
-                case Surface.ROTATION_180:
-                    outMatrix.setRotate(180);
-                    outMatrix.postTranslate(mHeight, mWidth);
-                    break;
-                case Surface.ROTATION_270:
-                    outMatrix.setRotate(90);
-                    outMatrix.postTranslate(mWidth, 0);
-                    break;
+            mTmpMatrix.reset();
+            postDisplayRotation(deltaRotation(mCurrentRotation, mRotation),
+                    mHeight, mWidth, mTmpMatrix);
+            if (forceTransform) {
+                if (DEBUG) {
+                    Log.d(TAG, "Transforming rotation due to forceTransform, "
+                            + "mCurrentRotation: " + mCurrentRotation
+                            + "mRotation: " + mRotation);
+                }
+                event.transform(mTmpMatrix);
+                return true;
             }
+            mTmpPoint[0] = event.getX();
+            mTmpPoint[1] = event.getY();
+            mTmpMatrix.mapPoints(mTmpPoint);
 
-            tmp.transform(outMatrix);
             if (DEBUG) {
                 Log.d(TAG, "original: " + event.getX() + ", " + event.getY()
-                                + " new: " + tmp.getX() + ", " + tmp.getY()
+                                + " new: " + mTmpPoint[0] + ", " + mTmpPoint[1]
                                 + " rect: " + this + " forceTransform: " + forceTransform
-                                + " contains: " + contains(tmp.getX(), tmp.getY()));
+                                + " contains: " + contains(mTmpPoint[0], mTmpPoint[1]));
             }
 
-            if (forceTransform || contains(tmp.getX(), tmp.getY())) {
-                event.transform(outMatrix);
-                tmp.recycle();
+            if (contains(mTmpPoint[0], mTmpPoint[1])) {
+                event.transform(mTmpMatrix);
                 return true;
             }
             return false;
         }
     }
+
+    /**
+     * @return how many factors {@param newRotation} is rotated 90 degrees clockwise.
+     * E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise...
+     * A value of 0 means no rotation has been applied
+     */
+    @SurfaceRotation
+    private static int deltaRotation(int oldRotation, int newRotation) {
+        int delta = newRotation - oldRotation;
+        if (delta < 0) delta += 4;
+        return delta;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index ba99016..1f1a999 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -33,9 +33,6 @@
 
 import java.lang.annotation.Retention;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
 public class LayoutUtils {
 
     private static final int MULTI_WINDOW_STRATEGY_HALF_SCREEN = 1;
@@ -68,7 +65,7 @@
                 // UI when shown.
                 extraSpace = 0;
             } else {
-                extraSpace = getDefaultSwipeHeight(context, dp) + dp.verticalDragHandleSizePx
+                extraSpace = getDefaultSwipeHeight(context, dp) + dp.workspacePageIndicatorHeight
                         + res.getDimensionPixelSize(
                                 R.dimen.dynamic_grid_hotseat_extra_vertical_size)
                         + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
new file mode 100644
index 0000000..f72e458
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static android.hardware.camera2.params.OutputConfiguration.ROTATION_180;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.view.MotionEvent;
+import android.view.Surface;
+
+import androidx.annotation.IntDef;
+
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.touch.PortraitPagedViewHandler;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Container to hold orientation/rotation related information for Launcher.
+ * This is not meant to be an abstraction layer for applying different functionality between
+ * the different orientation/rotations. For that see {@link PagedOrientationHandler}
+ *
+ * This class has initial default state assuming the device and foreground app have
+ * no ({@link Surface#ROTATION_0} rotation.
+ */
+public final class RecentsOrientedState {
+
+    @Retention(SOURCE)
+    @IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
+    public @interface SurfaceRotation {}
+
+    private PagedOrientationHandler mOrientationHandler = PagedOrientationHandler.PORTRAIT;
+
+    private @SurfaceRotation int mTouchRotation = ROTATION_0;
+    private @SurfaceRotation int mDisplayRotation = ROTATION_0;
+    /**
+     * If {@code true} we default to {@link PortraitPagedViewHandler} and don't support any fake
+     * launcher orientations.
+     */
+    private boolean mDisableMultipleOrientations;
+
+    private final Matrix mTmpMatrix = new Matrix();
+    private final Matrix mTmpInverseMatrix = new Matrix();
+
+    /**
+     * Sets the appropriate {@link PagedOrientationHandler} for {@link #mOrientationHandler}
+     * @param touchRotation The rotation the nav bar region that is touched is in
+     * @param displayRotation Rotation of the display/device
+     *
+     * @return true if there was any change in the internal state as a result of this call,
+     *         false otherwise
+     */
+    public boolean update(
+            @SurfaceRotation int touchRotation, @SurfaceRotation int displayRotation) {
+        if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
+            return false;
+        }
+        if (mDisableMultipleOrientations) {
+            return false;
+        }
+        if (mDisplayRotation == displayRotation && mTouchRotation == touchRotation) {
+            return false;
+        }
+
+        mDisplayRotation = displayRotation;
+        mTouchRotation = touchRotation;
+        if (mTouchRotation == ROTATION_90) {
+            mOrientationHandler = PagedOrientationHandler.LANDSCAPE;
+        } else if (mTouchRotation == ROTATION_270) {
+            mOrientationHandler = PagedOrientationHandler.SEASCAPE;
+        } else {
+            mOrientationHandler = PagedOrientationHandler.PORTRAIT;
+        }
+        return true;
+    }
+
+    public boolean areMultipleLayoutOrientationsDisabled() {
+        return mDisableMultipleOrientations;
+    }
+
+    /**
+     * Setting this preference will render future calls to {@link #update(int, int)} as a no-op.
+     */
+    public void disableMultipleOrientations(boolean disable) {
+        mDisableMultipleOrientations = disable;
+        if (disable) {
+            mDisplayRotation = mTouchRotation = ROTATION_0;
+            mOrientationHandler = PagedOrientationHandler.PORTRAIT;
+        }
+    }
+
+    @SurfaceRotation
+    public int getDisplayRotation() {
+        return mDisplayRotation;
+    }
+
+    @SurfaceRotation
+    public int getTouchRotation() {
+        return mTouchRotation;
+    }
+
+    public int getTouchRotationDegrees() {
+        switch (mTouchRotation) {
+            case ROTATION_90:
+                return 90;
+            case ROTATION_180:
+                return 180;
+            case ROTATION_270:
+                return 270;
+            case ROTATION_0:
+            default:
+                return 0;
+        }
+    }
+
+    public PagedOrientationHandler getOrientationHandler() {
+        return mOrientationHandler;
+    }
+
+    /**
+     * For landscape, since the navbar is already in a vertical position, we don't have to do any
+     * rotations as the change in Y coordinate is what is read. We only flip the sign of the
+     * y coordinate to make it match existing behavior of swipe to the top to go previous
+     */
+    public void flipVertical(MotionEvent ev) {
+        mTmpMatrix.setScale(1, -1);
+        ev.transform(mTmpMatrix);
+    }
+
+    /**
+     * Creates a matrix to transform the given motion event specified by degrees.
+     * If inverse is {@code true}, the inverse of that matrix will be applied
+     */
+    public void transformEvent(float degrees, MotionEvent ev, boolean inverse) {
+        mTmpMatrix.setRotate(inverse ? -degrees : degrees);
+        ev.transform(mTmpMatrix);
+
+        // TODO: Add scaling back in based on degrees
+        /*
+        if (getWidth() > 0 && getHeight() > 0) {
+            float scale = ((float) getWidth()) / getHeight();
+            transform.postScale(scale, 1 / scale);
+        }
+        */
+    }
+
+    public void mapRectFromNormalOrientation(RectF src, int screenWidth, int screenHeight) {
+        mTmpMatrix.reset();
+        postDisplayRotation(mDisplayRotation, screenWidth, screenHeight, mTmpMatrix);
+        mTmpMatrix.mapRect(src);
+    }
+
+    public void mapInverseRectFromNormalOrientation(RectF src, int screenWidth, int screenHeight) {
+        mTmpMatrix.reset();
+        postDisplayRotation(mDisplayRotation, screenWidth, screenHeight, mTmpMatrix);
+        mTmpMatrix.invert(mTmpInverseMatrix);
+        mTmpInverseMatrix.mapRect(src);
+    }
+
+    @SurfaceRotation
+    public static int getRotationForUserDegreesRotated(float degrees) {
+        int threshold = 70;
+        if (degrees >= (360 - threshold) || degrees < (threshold)) {
+            return ROTATION_0;
+        } else if (degrees < (90 + threshold)) {
+            return ROTATION_270;
+        } else if (degrees < 180 + threshold) {
+            return ROTATION_180;
+        } else {
+            return ROTATION_90;
+        }
+    }
+
+    /**
+     * Posts the transformation on the matrix representing the provided display rotation
+     */
+    public static void postDisplayRotation(@SurfaceRotation int displayRotation,
+            float screenWidth, float screenHeight, Matrix out) {
+        switch (displayRotation) {
+            case ROTATION_0:
+                return;
+            case ROTATION_90:
+                out.postRotate(270);
+                out.postTranslate(0, screenWidth);
+                break;
+            case ROTATION_180:
+                out.postRotate(180);
+                out.postTranslate(screenHeight, screenWidth);
+                break;
+            case ROTATION_270:
+                out.postRotate(90);
+                out.postTranslate(screenHeight, 0);
+                break;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 14c458e..c2ccd90 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -88,7 +88,6 @@
 
     private float mShiftRange;
 
-    private final float mShelfOffset;
     private float mTopOffset;
     private float mShelfTop;
     private float mShelfTopAtThreshold;
@@ -110,7 +109,6 @@
         mRadius = BOTTOM_CORNER_RADIUS_RATIO * Themes.getDialogCornerRadius(context);
         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 
-        mShelfOffset = context.getResources().getDimension(R.dimen.shelf_surface_offset);
         // Just assume the easiest UI for now, until we have the proper layout information.
         mDrawingFlatColor = true;
     }
@@ -179,7 +177,7 @@
                         Math.min(hotseatSize, LayoutUtils.getDefaultSwipeHeight(context, dp));
                 mDragHandleProgress =  1 - (dragHandleTop / mShiftRange);
             }
-            mTopOffset = dp.getInsets().top - mShelfOffset;
+            mTopOffset = dp.getInsets().top - mDragHandleSize.y;
             mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset;
         }
         updateColors();
@@ -190,12 +188,15 @@
     @Override
     public void updateColors() {
         super.updateColors();
+        mDragHandleOffset = 0;
         if (mDrawingFlatColor) {
-            mDragHandleOffset = 0;
             return;
         }
 
-        mDragHandleOffset = mShelfOffset - mDragHandleSize;
+        if (mProgress < mDragHandleProgress) {
+            mDragHandleOffset = mShiftRange * (mDragHandleProgress - mProgress);
+        }
+
         if (mProgress >= SCRIM_CATCHUP_THRESHOLD) {
             mShelfTop = mShiftRange * mProgress + mTopOffset;
         } else {
@@ -231,10 +232,6 @@
                             (float) 0, LINEAR));
             mRemainingScreenColor = setColorAlphaBound(mScrimColor, remainingScrimAlpha);
         }
-
-        if (mProgress < mDragHandleProgress) {
-            mDragHandleOffset += mShiftRange * (mDragHandleProgress - mProgress);
-        }
     }
 
     @Override
@@ -290,4 +287,9 @@
         mPaint.setColor(mShelfColor);
         canvas.drawRoundRect(0, mShelfTop, width, height + mRadius, mRadius, mRadius, mPaint);
     }
+
+    @Override
+    public float getVisualTop() {
+        return mShelfTop;
+    }
 }
diff --git a/res/drawable-v24/drag_handle_indicator_shadow.xml b/res/drawable-v24/drag_handle_indicator_shadow.xml
new file mode 100644
index 0000000..774bc38
--- /dev/null
+++ b/res/drawable-v24/drag_handle_indicator_shadow.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.graphics.ShadowDrawable
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/drag_handle_indicator_no_shadow"
+    android:elevation="@dimen/vertical_drag_handle_elevation" />
diff --git a/res/drawable/drag_handle_indicator.xml b/res/drawable/drag_handle_indicator.xml
deleted file mode 100644
index b01b84a..0000000
--- a/res/drawable/drag_handle_indicator.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="@dimen/vertical_drag_handle_size"
-    android:height="@dimen/vertical_drag_handle_size"
-    android:viewportWidth="36.0"
-    android:viewportHeight="36.0" >
-
-    <group
-        android:translateX="11.5"
-        android:translateY="11.5">
-        <path
-            android:pathData="M2 8.5L6.5 4L11 8.5"
-            android:strokeColor="?attr/workspaceAmbientShadowColor"
-            android:strokeWidth="3.6"
-            android:strokeLineCap="round"
-            android:strokeLineJoin="round" />
-
-        <path
-            android:pathData="M2 8.5L6.5 4L11 8.5"
-            android:strokeColor="?attr/workspaceTextColor"
-            android:strokeWidth="1.8"
-            android:strokeLineCap="round"
-            android:strokeLineJoin="round" />
-        </group>
-</vector>
diff --git a/res/drawable/drag_handle_indicator_no_shadow.xml b/res/drawable/drag_handle_indicator_no_shadow.xml
new file mode 100644
index 0000000..341e60c
--- /dev/null
+++ b/res/drawable/drag_handle_indicator_no_shadow.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/vertical_drag_handle_width"
+    android:height="@dimen/vertical_drag_handle_height"
+    android:viewportWidth="18.0"
+    android:viewportHeight="6.0"
+    android:tint="?attr/workspaceTextColor" >
+
+    <path
+        android:pathData="M17,6c-0.15,0-0.3-0.03-0.45-0.11L9,2.12L1.45,5.89c-0.5,0.25-1.09,
+        0.05-1.34-0.45S0.06,4.35,0.55,4.11l8-4c0.28-0.14,0.61-0.14,0.89,0l8,4c0.49,0.25,0.69,
+        0.85,0.45,1.34C17.72,5.8,17.37,6,17,6z"
+        android:fillColor="@android:color/white" />
+</vector>
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index ab6c960..de13277 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -57,7 +57,7 @@
         <com.android.launcher3.pageindicators.WorkspacePageIndicator
             android:id="@+id/page_indicator"
             android:layout_width="match_parent"
-            android:layout_height="@dimen/vertical_drag_handle_size"
+            android:layout_height="@dimen/workspace_page_indicator_height"
             android:layout_gravity="bottom|center_horizontal"
             android:theme="@style/HomeScreenElementTheme" />
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 271511e..0b589a2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -20,7 +20,6 @@
 
     <!-- Dynamic Grid -->
     <dimen name="dynamic_grid_edge_margin">8dp</dimen>
-    <dimen name="dynamic_grid_page_indicator_line_height">1dp</dimen>
     <dimen name="dynamic_grid_icon_drawable_padding">8dp</dimen>
     <!-- Minimum space between workspace and hotseat in spring loaded mode -->
     <dimen name="dynamic_grid_min_spring_loaded_space">8dp</dimen>
@@ -36,10 +35,18 @@
     <dimen name="dynamic_grid_hotseat_extra_vertical_size">34dp</dimen>
     <dimen name="dynamic_grid_hotseat_side_padding">0dp</dimen>
 
+    <!-- Workspace page indicator -->
+    <dimen name="workspace_page_indicator_height">24dp</dimen>
+    <dimen name="workspace_page_indicator_line_height">1dp</dimen>
+    <dimen name="workspace_page_indicator_overlap_workspace">0dp</dimen>
+
     <!-- Hotseat/all-apps scrim -->
     <dimen name="all_apps_scrim_blur">4dp</dimen>
-    <dimen name="vertical_drag_handle_size">24dp</dimen>
-    <dimen name="vertical_drag_handle_overlap_workspace">0dp</dimen>
+    <dimen name="vertical_drag_handle_width">18dp</dimen>
+    <dimen name="vertical_drag_handle_height">6dp</dimen>
+    <dimen name="vertical_drag_handle_elevation">1dp</dimen>
+    <dimen name="vertical_drag_handle_touch_size">48dp</dimen>
+    <dimen name="vertical_drag_handle_padding_in_vertical_bar_layout">16dp</dimen>
 
 <!-- Drop target bar -->
     <dimen name="dynamic_grid_drop_target_size">48dp</dimen>
diff --git a/res/values/drawables.xml b/res/values/drawables.xml
index 9c57ec1..7d63142 100644
--- a/res/values/drawables.xml
+++ b/res/values/drawables.xml
@@ -18,4 +18,5 @@
     <drawable name="ic_remove_shadow">@drawable/ic_remove_no_shadow</drawable>
     <drawable name="ic_uninstall_shadow">@drawable/ic_uninstall_no_shadow</drawable>
     <drawable name="ic_block_shadow">@drawable/ic_block_no_shadow</drawable>
+    <drawable name="all_apps_arrow_shadow">@drawable/drag_handle_indicator_no_shadow</drawable>
 </resources>
\ No newline at end of file
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index c049069..4e1e586 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -76,9 +76,9 @@
     public float workspaceSpringLoadShrinkFactor;
     public final int workspaceSpringLoadedBottomSpace;
 
-    // Drag handle
-    public final int verticalDragHandleSizePx;
-    private final int verticalDragHandleOverlapWorkspace;
+    // Workspace page indicator
+    public final int workspacePageIndicatorHeight;
+    private final int mWorkspacePageIndicatorOverlapWorkspace;
 
     // Workspace icons
     public int iconSizePx;
@@ -190,10 +190,10 @@
             cellLayoutBottomPaddingPx = 0;
         }
 
-        verticalDragHandleSizePx = res.getDimensionPixelSize(
-                R.dimen.vertical_drag_handle_size);
-        verticalDragHandleOverlapWorkspace =
-                res.getDimensionPixelSize(R.dimen.vertical_drag_handle_overlap_workspace);
+        workspacePageIndicatorHeight = res.getDimensionPixelSize(
+                R.dimen.workspace_page_indicator_height);
+        mWorkspacePageIndicatorOverlapWorkspace =
+                res.getDimensionPixelSize(R.dimen.workspace_page_indicator_overlap_workspace);
 
         iconDrawablePaddingOriginalPx =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
@@ -211,7 +211,7 @@
         hotseatBarSidePaddingEndPx =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
         // Add a bit of space between nav bar and hotseat in vertical bar layout.
-        hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? verticalDragHandleSizePx : 0;
+        hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
         hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, dm) + (isVerticalBarLayout()
                 ? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx)
                 : (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
@@ -227,7 +227,7 @@
             // in portrait mode closer together by adding more height to the hotseat.
             // Note: This calculation was created after noticing a pattern in the design spec.
             int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2
-                    - verticalDragHandleSizePx;
+                    - workspacePageIndicatorHeight;
             hotseatBarSizePx += extraSpace;
             hotseatBarBottomPaddingPx += extraSpace;
 
@@ -376,7 +376,7 @@
 
         if (!isVerticalLayout) {
             int expectedWorkspaceHeight = availableHeightPx - hotseatBarSizePx
-                    - verticalDragHandleSizePx - edgeMarginPx;
+                    - workspacePageIndicatorHeight - edgeMarginPx;
             float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace;
             workspaceSpringLoadShrinkFactor = Math.min(
                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f,
@@ -480,14 +480,14 @@
             padding.bottom = edgeMarginPx;
             if (isSeascape()) {
                 padding.left = hotseatBarSizePx;
-                padding.right = verticalDragHandleSizePx;
+                padding.right = hotseatBarSidePaddingStartPx;
             } else {
-                padding.left = verticalDragHandleSizePx;
+                padding.left = hotseatBarSidePaddingStartPx;
                 padding.right = hotseatBarSizePx;
             }
         } else {
-            int paddingBottom = hotseatBarSizePx + verticalDragHandleSizePx
-                    - verticalDragHandleOverlapWorkspace;
+            int paddingBottom = hotseatBarSizePx + workspacePageIndicatorHeight
+                    - mWorkspacePageIndicatorOverlapWorkspace;
             if (isTablet) {
                 // Pad the left and right of the workspace to ensure consistent spacing
                 // between all icons
@@ -554,7 +554,7 @@
                     mInsets.top + dropTargetBarSizePx + edgeMarginPx,
                     mInsets.left + availableWidthPx - edgeMarginPx,
                     mInsets.top + availableHeightPx - hotseatBarSizePx
-                            - verticalDragHandleSizePx - edgeMarginPx);
+                            - workspacePageIndicatorHeight - edgeMarginPx);
         }
     }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 03817ad..043ea2f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1140,7 +1140,7 @@
         // Setup the drag controller (drop targets have to be added in reverse order in priority)
         mDropTargetBar.setup(mDragController);
 
-        mAllAppsController.setupViews(mAppsView);
+        mAllAppsController.setupViews(mAppsView, mScrimView);
     }
 
     /**
@@ -1362,6 +1362,10 @@
         return mDropTargetBar;
     }
 
+    public ScrimView getScrimView() {
+        return mScrimView;
+    }
+
     public LauncherAppWidgetHost getAppWidgetHost() {
         return mAppWidgetHost;
     }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 5e47e2f..e36ae94 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -38,7 +38,6 @@
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
-import android.view.Surface;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -55,12 +54,10 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.PagedViewOrientedState;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.touch.OverScroll;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds;
-import com.android.launcher3.touch.PortraitPagedViewHandler;
 import com.android.launcher3.util.OverScroller;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ActivityContext;
@@ -121,8 +118,7 @@
     private float mLastMotion;
     private float mLastMotionRemainder;
     private float mTotalMotion;
-    protected PagedOrientationHandler mOrientationHandler = new PortraitPagedViewHandler();
-    protected final PagedViewOrientedState mOrientationState = new PagedViewOrientedState();
+    protected PagedOrientationHandler mOrientationHandler = PagedOrientationHandler.PORTRAIT;
 
     protected int[] mPageScrolls;
     private boolean mIsBeingDragged;
@@ -144,9 +140,6 @@
 
     protected int mUnboundedScroll;
 
-    protected int mLayoutRotation = Surface.ROTATION_0;
-    protected int mDisplayRotation = Surface.ROTATION_0;
-
     // Page Indicator
     @Thunk int mPageIndicatorViewId;
     protected T mPageIndicator;
@@ -417,37 +410,6 @@
         return mUnboundedScroll;
     }
 
-    protected void updateLayoutRotation(int touchRotation) {
-        setLayoutRotation(touchRotation, mDisplayRotation);
-    }
-
-    /** @param touchRotation Must be one of {@link android.view.Surface.ROTATION_0/90/180/270} */
-    public void setLayoutRotation(int touchRotation, int displayRotation) {
-        if (mLayoutRotation == touchRotation && mDisplayRotation == displayRotation) {
-            return;
-        }
-
-        mOrientationState.update(touchRotation, displayRotation);
-        mOrientationHandler = mOrientationState.getOrientationHandler();
-        mLayoutRotation = touchRotation;
-        mDisplayRotation = displayRotation;
-        requestLayout();
-    }
-
-    public PagedViewOrientedState getPagedViewOrientedState() {
-        return mOrientationState;
-    }
-
-    public PagedOrientationHandler getPagedOrientationHandler() {
-        return getPagedViewOrientedState().getOrientationHandler();
-    }
-
-    public void disableMultipleLayoutRotations(boolean disable) {
-        mOrientationState.disableMultipleOrientations(disable);
-        mOrientationHandler = mOrientationState.getOrientationHandler();
-        requestLayout();
-    }
-
     @Override
     public void scrollBy(int x, int y) {
         mOrientationHandler.delegateScrollBy(this, getUnboundedScroll(), x, y);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index fc40fa6..7c504a6 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -124,7 +124,7 @@
 
     private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
 
-    private static final int DEFAULT_PAGE = 0;
+    public static final int DEFAULT_PAGE = 0;
 
     private LayoutTransition mLayoutTransition;
     @Thunk final WallpaperManager mWallpaperManager;
@@ -282,6 +282,7 @@
 
         Rect padding = grid.workspacePadding;
         setPadding(padding.left, padding.top, padding.right, padding.bottom);
+        mInsets.set(insets);
 
         if (mWorkspaceFadeInAdjacentScreens) {
             // In landscape mode the page spacing is set to the default.
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 7600f52..68b0706 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -123,8 +123,8 @@
 
         // Use a light system UI (dark icons) if all apps is behind at least half of the
         // status bar.
-        boolean forceChange = shiftCurrent - mScrimView.getDragHandleSize()
-                <= mLauncher.getDeviceProfile().getInsets().top / 2;
+        boolean forceChange = Math.min(shiftCurrent, mScrimView.getVisualTop())
+                <= mLauncher.getDeviceProfile().getInsets().top / 2f;
         if (forceChange) {
             mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, !mIsDarkTheme);
         } else {
@@ -212,9 +212,9 @@
         return AnimationSuccessListener.forRunnable(this::onProgressAnimationEnd);
     }
 
-    public void setupViews(AllAppsContainerView appsView) {
+    public void setupViews(AllAppsContainerView appsView, ScrimView scrimView) {
         mAppsView = appsView;
-        mScrimView = mLauncher.findViewById(R.id.scrim_view);
+        mScrimView = scrimView;
     }
 
     /**
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index ec34350..bcd91da 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -92,8 +92,8 @@
     public static final BooleanFlag ENABLE_QUICKSTEP_LIVE_TILE = getDebugFlag(
             "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
 
-    public static final BooleanFlag ENABLE_HINTS_IN_OVERVIEW = getDebugFlag(
-            "ENABLE_HINTS_IN_OVERVIEW", false, "Show chip hints and gleams on the overview screen");
+    public static final BooleanFlag ENABLE_SUGGESTED_ACTIONS_OVERVIEW = new DeviceFlag(
+            "ENABLE_SUGGESTED_ACTIONS_OVERVIEW", false, "Show chip hints on the overview screen");
 
     public static final BooleanFlag FOLDER_NAME_SUGGEST = new DeviceFlag(
             "FOLDER_NAME_SUGGEST", true,
diff --git a/src/com/android/launcher3/graphics/GridOptionsProvider.java b/src/com/android/launcher3/graphics/GridOptionsProvider.java
index 607aba9..af974f8 100644
--- a/src/com/android/launcher3/graphics/GridOptionsProvider.java
+++ b/src/com/android/launcher3/graphics/GridOptionsProvider.java
@@ -20,7 +20,6 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.GridOption;
 import com.android.launcher3.R;
-import com.android.launcher3.uioverrides.PreviewSurfaceRenderer;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -201,12 +200,11 @@
     }
 
     @Override
-    public Bundle call(String method, String arg, Bundle extras)  {
+    public Bundle call(String method, String arg, Bundle extras) {
         if (!METHOD_GET_PREVIEW.equals(method)) {
             return null;
         }
 
-        PreviewSurfaceRenderer.render(getContext(), extras);
-        return null;
+        return new PreviewSurfaceRenderer(getContext(), extras).render();
     }
 }
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
new file mode 100644
index 0000000..20eec9a
--- /dev/null
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 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.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.view.Display;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+
+import com.android.launcher3.InvariantDeviceProfile;
+
+import java.util.concurrent.TimeUnit;
+
+/** Render preview using surface view. */
+public class PreviewSurfaceRenderer implements IBinder.DeathRecipient {
+
+    private static final String KEY_HOST_TOKEN = "host_token";
+    private static final String KEY_VIEW_WIDTH = "width";
+    private static final String KEY_VIEW_HEIGHT = "height";
+    private static final String KEY_DISPLAY_ID = "display_id";
+    private static final String KEY_SURFACE_PACKAGE = "surface_package";
+    private static final String KEY_CALLBACK = "callback";
+
+    private final Context mContext;
+    private final InvariantDeviceProfile mIdp;
+    private final IBinder mHostToken;
+    private final int mWidth;
+    private final int mHeight;
+    private final Display mDisplay;
+
+    private SurfaceControlViewHost mSurfaceControlViewHost;
+
+    PreviewSurfaceRenderer(Context context, Bundle bundle) {
+        mContext = context;
+
+        String gridName = bundle.getString("name");
+        bundle.remove("name");
+        if (gridName == null) {
+            gridName = InvariantDeviceProfile.getCurrentGridName(context);
+        }
+        mIdp = new InvariantDeviceProfile(context, gridName);
+
+        mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
+        mWidth = bundle.getInt(KEY_VIEW_WIDTH);
+        mHeight = bundle.getInt(KEY_VIEW_HEIGHT);
+
+        final DisplayManager displayManager = (DisplayManager) context.getSystemService(
+                Context.DISPLAY_SERVICE);
+        mDisplay = displayManager.getDisplay(bundle.getInt(KEY_DISPLAY_ID));
+    }
+
+    /** Handle a received surface view request. */
+    Bundle render() {
+        if (mSurfaceControlViewHost != null) {
+            binderDied();
+        }
+
+        try {
+            mSurfaceControlViewHost = MAIN_EXECUTOR
+                    .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken))
+                    .get(5, TimeUnit.SECONDS);
+            mHostToken.linkToDeath(this, 0);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+
+        MAIN_EXECUTOR.execute(() -> {
+            View view = new LauncherPreviewRenderer(mContext, mIdp).getRenderedView();
+            // This aspect scales the view to fit in the surface and centers it
+            final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
+                    mHeight / (float) view.getMeasuredHeight());
+            view.setScaleX(scale);
+            view.setScaleY(scale);
+            view.setPivotX(0);
+            view.setPivotY(0);
+            view.setTranslationX((mWidth - scale * view.getWidth()) / 2);
+            view.setTranslationY((mHeight - scale * view.getHeight()) / 2);
+            mSurfaceControlViewHost.setView(view, view.getMeasuredWidth(),
+                    view.getMeasuredHeight());
+        });
+
+        Bundle result = new Bundle();
+        result.putParcelable(KEY_SURFACE_PACKAGE, mSurfaceControlViewHost.getSurfacePackage());
+
+        Handler handler = new Handler(Looper.getMainLooper(), Loopermessage -> {
+            binderDied();
+            return true;
+        });
+        Messenger messenger = new Messenger(handler);
+        Message msg = Message.obtain();
+        msg.replyTo = messenger;
+        result.putParcelable(KEY_CALLBACK, msg);
+        return result;
+    }
+
+    @Override
+    public void binderDied() {
+        if (mSurfaceControlViewHost != null) {
+            mSurfaceControlViewHost.release();
+            mSurfaceControlViewHost = null;
+        }
+        mHostToken.unlinkToDeath(this, 0);
+    }
+}
diff --git a/src/com/android/launcher3/model/PagedViewOrientedState.java b/src/com/android/launcher3/model/PagedViewOrientedState.java
deleted file mode 100644
index e48b8c1..0000000
--- a/src/com/android/launcher3/model/PagedViewOrientedState.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- *
- *  * Copyright (C) 2020 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.model;
-
-import android.view.Surface;
-
-import com.android.launcher3.states.RotationHelper;
-import com.android.launcher3.touch.PortraitPagedViewHandler;
-import com.android.launcher3.touch.LandscapePagedViewHandler;
-import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.touch.SeascapePagedViewHandler;
-
-/**
- * Container to hold orientation/rotation related information for Launcher.
- * This is not meant to be an abstraction layer for applying different functionality between
- * the different orientation/rotations. For that see {@link PagedOrientationHandler}
- *
- * This class has initial default state assuming the device and foreground app have
- * no ({@link Surface.ROTATION_0} rotation.
- *
- * Currently this class resides in {@link com.android.launcher3.PagedView}, but there's a ticket
- * to disassociate it from Launcher since it's needed before Launcher is instantiated
- * See TODO(b/150300347)
- */
-public final class PagedViewOrientedState {
-
-    private PagedOrientationHandler mOrientationHandler = new PortraitPagedViewHandler();
-
-    private int mTouchRotation = Surface.ROTATION_0;
-    private int mDisplayRotation = Surface.ROTATION_0;
-    /**
-     * If {@code true} we default to {@link PortraitPagedViewHandler} and don't support any fake
-     * launcher orientations.
-     */
-    private boolean mDisableMultipleOrientations;
-
-    /**
-     * Sets the appropriate {@link PagedOrientationHandler} for {@link #mOrientationHandler}
-     * @param touchRotation The rotation the nav bar region that is touched is in
-     * @param displayRotation Rotation of the display/device
-     */
-    public void update(int touchRotation, int displayRotation) {
-        if (mDisableMultipleOrientations) {
-            return;
-        }
-
-        mDisplayRotation = displayRotation;
-        mTouchRotation = touchRotation;
-        if (mTouchRotation == Surface.ROTATION_90) {
-            mOrientationHandler = new LandscapePagedViewHandler();
-        } else if (mTouchRotation == Surface.ROTATION_270) {
-            mOrientationHandler = new SeascapePagedViewHandler();
-        } else {
-            mOrientationHandler = new PortraitPagedViewHandler();
-        }
-    }
-
-    public boolean areMultipleLayoutOrientationsDisabled() {
-        return mDisableMultipleOrientations;
-    }
-
-    /**
-     * Setting this preference will render future calls to {@link #update(int, int)} as a no-op.
-     */
-    public void disableMultipleOrientations(boolean disable) {
-        mDisableMultipleOrientations = disable;
-        if (disable) {
-            mOrientationHandler = new PortraitPagedViewHandler();
-        }
-    }
-
-    public int getDisplayRotation() {
-        return mDisplayRotation;
-    }
-
-    /**
-     * Gets the difference between the rotation of the device/display and which region the
-     * user is currently interacting with in factors of 90 degree clockwise rotations.
-     * Ex. Display is in portrait -> 0, user touches landscape region -> 1, this
-     * method would return 3 because it takes 3 clockwise 90 degree rotations from normal to
-     * landscape (portrait -> seascape -> reverse portrait -> landscape)
-     */
-    public int getTouchDisplayDelta() {
-        return RotationHelper.deltaRotation(mTouchRotation, mDisplayRotation);
-    }
-
-    public PagedOrientationHandler getOrientationHandler() {
-        return mOrientationHandler;
-    }
-}
diff --git a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
index 0f2ca72..408796f 100644
--- a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
@@ -121,7 +121,7 @@
         mLinePaint.setAlpha(0);
 
         mLauncher = Launcher.getLauncher(context);
-        mLineHeight = res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_line_height);
+        mLineHeight = res.getDimensionPixelSize(R.dimen.workspace_page_indicator_line_height);
 
         boolean darkText = WallpaperColorInfo.INSTANCE.get(context).supportsDarkText();
         mActiveAlpha = darkText ? BLACK_ALPHA : WHITE_ALPHA;
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
index 290dbb6..43f30f1 100644
--- a/src/com/android/launcher3/states/HintState.java
+++ b/src/com/android/launcher3/states/HintState.java
@@ -17,6 +17,7 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
@@ -50,8 +51,13 @@
 
     @Override
     public void onStateTransitionEnd(Launcher launcher) {
-        launcher.getStateManager().goToState(NORMAL);
+        LauncherStateManager stateManager = launcher.getStateManager();
         Workspace workspace = launcher.getWorkspace();
-        workspace.post(workspace::moveToDefaultScreen);
+        boolean willMoveScreens = workspace.getNextPage() != Workspace.DEFAULT_PAGE;
+        stateManager.goToState(NORMAL, true, willMoveScreens ? null
+                : launcher.getScrimView()::startDragHandleEducationAnim);
+        if (willMoveScreens) {
+            workspace.post(workspace::moveToDefaultScreen);
+        }
     }
 }
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 8bb6a08..2e0521f 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -31,12 +31,7 @@
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.res.Resources;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
 import android.provider.Settings;
-import android.view.MotionEvent;
-import android.view.Surface;
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -258,138 +253,6 @@
         }
     }
 
-    public static int getDegreesFromRotation(int rotation) {
-        int degrees;
-        switch (rotation) {
-            case Surface.ROTATION_90:
-                degrees = 90;
-                break;
-            case Surface.ROTATION_180:
-                degrees = 180;
-                break;
-            case Surface.ROTATION_270:
-                degrees = 270;
-                break;
-            case Surface.ROTATION_0:
-            default:
-                degrees = 0;
-                break;
-        }
-        return degrees;
-    }
-
-    public static int getRotationFromDegrees(float degrees) {
-        int threshold = 70;
-        if (degrees >= (360 - threshold) || degrees < (threshold)) {
-            return Surface.ROTATION_0;
-        } else if (degrees < (90 + threshold)) {
-            return Surface.ROTATION_270;
-        } else if (degrees < 180 + threshold) {
-            return Surface.ROTATION_180;
-        } else {
-            return Surface.ROTATION_90;
-        }
-    }
-
-    /**
-     * @return how many factors {@param newRotation} is rotated 90 degrees clockwise.
-     * E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise...
-     * A value of 0 means no rotation has been applied
-     */
-    public static int deltaRotation(int oldRotation, int newRotation) {
-        int delta = newRotation - oldRotation;
-        if (delta < 0) delta += 4;
-        return delta;
-    }
-
-    /**
-     * For landscape, since the navbar is already in a vertical position, we don't have to do any
-     * rotations as the change in Y coordinate is what is read. We only flip the sign of the
-     * y coordinate to make it match existing behavior of swipe to the top to go previous
-     */
-    public static void transformEventForNavBar(MotionEvent ev, boolean inverse) {
-        // TODO(b/151269990): Use a temp matrix
-        Matrix m = new Matrix();
-        m.setScale(1, -1);
-        if (inverse) {
-            Matrix inv = new Matrix();
-            m.invert(inv);
-            ev.transform(inv);
-        } else {
-            ev.transform(m);
-        }
-    }
-
-    /**
-     * Creates a matrix to transform the given motion event specified by degrees.
-     * If {@param inverse} is {@code true}, the inverse of that matrix will be applied
-     */
-    public static void transformEvent(float degrees, MotionEvent ev, boolean inverse) {
-        Matrix transform = new Matrix();
-        // TODO(b/151269990): Use a temp matrix
-        transform.setRotate(degrees);
-        if (inverse) {
-            Matrix inv = new Matrix();
-            transform.invert(inv);
-            ev.transform(inv);
-        } else {
-            ev.transform(transform);
-        }
-        // TODO: Add scaling back in based on degrees
-//        if (getWidth() > 0 && getHeight() > 0) {
-//            float scale = ((float) getWidth()) / getHeight();
-//            transform.postScale(scale, 1 / scale);
-//        }
-    }
-
-    /**
-     * TODO(b/149658423): Have {@link com.android.quickstep.OrientationTouchTransformer
-     *   also use this}
-     */
-    public static Matrix getRotationMatrix(int screenWidth, int screenHeight, int displayRotation) {
-        Matrix m = new Matrix();
-        // TODO(b/151269990): Use a temp matrix
-        switch (displayRotation) {
-            case Surface.ROTATION_0:
-                return m;
-            case Surface.ROTATION_90:
-                m.setRotate(360 - RotationHelper.getDegreesFromRotation(displayRotation));
-                m.postTranslate(0, screenWidth);
-                break;
-            case Surface.ROTATION_270:
-                m.setRotate(360 - RotationHelper.getDegreesFromRotation(displayRotation));
-                m.postTranslate(screenHeight, 0);
-                break;
-        }
-        return m;
-    }
-
-    public static void mapRectFromNormalOrientation(RectF src, int screenWidth, int screenHeight,
-        int displayRotation) {
-        Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation);
-        m.mapRect(src);
-    }
-
-    public static void mapInverseRectFromNormalOrientation(RectF src, int screenWidth,
-        int screenHeight, int displayRotation) {
-        Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation);
-        Matrix inverse = new Matrix();
-        m.invert(inverse);
-        inverse.mapRect(src);
-    }
-
-    public static void getTargetRectForRotation(Rect srcOut, int screenWidth, int screenHeight,
-        int displayRotation) {
-        RectF wrapped = new RectF(srcOut);
-        Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation);
-        m.mapRect(wrapped);
-        wrapped.round(srcOut);
-    }
-
-    public static boolean isRotationLandscape(int rotation) {
-        return rotation == Surface.ROTATION_270 || rotation == Surface.ROTATION_90;
-    }
-
     @Override
     public String toString() {
         return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d,"
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 24fa815..974e3f3 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -39,6 +39,10 @@
  */
 public interface PagedOrientationHandler {
 
+    PagedOrientationHandler PORTRAIT = new PortraitPagedViewHandler();
+    PagedOrientationHandler LANDSCAPE = new LandscapePagedViewHandler();
+    PagedOrientationHandler SEASCAPE = new SeascapePagedViewHandler();
+
     interface Int2DAction<T> {
         void call(T target, int x, int y);
     }
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index 6d204f6..39e1eac 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -22,8 +22,10 @@
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 
 import android.animation.Animator;
@@ -33,8 +35,10 @@
 import android.animation.PropertyValuesHolder;
 import android.animation.RectEvaluator;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
@@ -98,6 +102,12 @@
     private static final int SETTINGS = R.string.settings_button_text;
     private static final int ALPHA_CHANNEL_COUNT = 1;
 
+    private static final long DRAG_HANDLE_BOUNCE_DURATION_MS = 300;
+    // How much to delay before repeating the bounce.
+    private static final long DRAG_HANDLE_BOUNCE_DELAY_MS = 200;
+    // Repeat this many times (i.e. total number of bounces is 1 + this).
+    private static final int DRAG_HANDLE_BOUNCE_REPEAT_COUNT = 2;
+
     private final Rect mTempRect = new Rect();
     private final int[] mTempPos = new int[2];
 
@@ -115,10 +125,13 @@
     protected int mEndFlatColor;
     protected int mEndFlatColorAlpha;
 
-    protected final int mDragHandleSize;
+    protected final Point mDragHandleSize;
+    private final int mDragHandleTouchSize;
+    private final int mDragHandlePaddingInVerticalBarLayout;
     protected float mDragHandleOffset;
     private final Rect mDragHandleBounds;
     private final RectF mHitRect = new RectF();
+    private ObjectAnimator mDragHandleAnim;
 
     private final MultiValueAlpha mMultiValueAlpha;
 
@@ -136,9 +149,13 @@
 
         mMaxScrimAlpha = 0.7f;
 
-        mDragHandleSize = context.getResources()
-                .getDimensionPixelSize(R.dimen.vertical_drag_handle_size);
-        mDragHandleBounds = new Rect(0, 0, mDragHandleSize, mDragHandleSize);
+        Resources res = context.getResources();
+        mDragHandleSize = new Point(res.getDimensionPixelSize(R.dimen.vertical_drag_handle_width),
+                res.getDimensionPixelSize(R.dimen.vertical_drag_handle_height));
+        mDragHandleBounds = new Rect(0, 0, mDragHandleSize.x, mDragHandleSize.y);
+        mDragHandleTouchSize = res.getDimensionPixelSize(R.dimen.vertical_drag_handle_touch_size);
+        mDragHandlePaddingInVerticalBarLayout = context.getResources()
+                .getDimensionPixelSize(R.dimen.vertical_drag_handle_padding_in_vertical_bar_layout);
 
         mAccessibilityHelper = createAccessibilityHelper();
         ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper);
@@ -204,6 +221,7 @@
     public void setProgress(float progress) {
         if (mProgress != progress) {
             mProgress = progress;
+            stopDragHandleEducationAnim();
             updateColors();
             updateDragHandleAlpha();
             invalidate();
@@ -251,70 +269,103 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        boolean value = super.onTouchEvent(event);
-        if (!value && mDragHandle != null && event.getAction() == ACTION_DOWN
-                && mDragHandle.getAlpha() == 255
-                && mHitRect.contains(event.getX(), event.getY())) {
-
-            final Drawable drawable = mDragHandle;
-            mDragHandle = null;
-
-            Rect bounds = new Rect(mDragHandleBounds);
-            bounds.offset(0, -(int) mDragHandleOffset);
-            drawable.setBounds(bounds);
-
-            Rect topBounds = new Rect(bounds);
-            topBounds.offset(0, -bounds.height() / 2);
-
-            Rect invalidateRegion = new Rect(bounds);
-            invalidateRegion.top = topBounds.top;
-
-            Keyframe frameTop = Keyframe.ofObject(0.6f, topBounds);
-            frameTop.setInterpolator(DEACCEL);
-            Keyframe frameBot = Keyframe.ofObject(1, bounds);
-            frameBot.setInterpolator(ACCEL);
-            PropertyValuesHolder holder = PropertyValuesHolder .ofKeyframe("bounds",
-                    Keyframe.ofObject(0, bounds), frameTop, frameBot);
-            holder.setEvaluator(new RectEvaluator());
-
-            ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, holder);
-            anim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    getOverlay().remove(drawable);
-                    updateDragHandleVisibility(drawable);
+        boolean superHandledTouch = super.onTouchEvent(event);
+        if (event.getAction() == ACTION_DOWN) {
+            if (!superHandledTouch && mHitRect.contains(event.getX(), event.getY())) {
+                if (startDragHandleEducationAnim()) {
+                    return true;
                 }
-            });
-            anim.addUpdateListener((v) -> invalidate(invalidateRegion));
-            getOverlay().add(drawable);
-            anim.start();
-            return true;
+            }
+            stopDragHandleEducationAnim();
         }
-        return value;
+        return superHandledTouch;
+    }
+
+    /**
+     * Animates the drag handle to demonstrate how to get to all apps.
+     * @return Whether the animation was started (false if drag handle is invisible).
+     */
+    public boolean startDragHandleEducationAnim() {
+        stopDragHandleEducationAnim();
+
+        if (mDragHandle == null || mDragHandle.getAlpha() != 255) {
+            return false;
+        }
+
+        final Drawable drawable = mDragHandle;
+        mDragHandle = null;
+
+        Rect bounds = new Rect(mDragHandleBounds);
+        bounds.offset(0, -(int) mDragHandleOffset);
+        drawable.setBounds(bounds);
+
+        Rect topBounds = new Rect(bounds);
+        topBounds.offset(0, -bounds.height());
+
+        Rect invalidateRegion = new Rect(bounds);
+        invalidateRegion.top = topBounds.top;
+
+        final float progressToReachTop = 0.6f;
+        Keyframe frameTop = Keyframe.ofObject(progressToReachTop, topBounds);
+        frameTop.setInterpolator(DEACCEL);
+        Keyframe frameBot = Keyframe.ofObject(1, bounds);
+        frameBot.setInterpolator(ACCEL_DEACCEL);
+        PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("bounds",
+                Keyframe.ofObject(0, bounds), frameTop, frameBot);
+        holder.setEvaluator(new RectEvaluator());
+
+        mDragHandleAnim = ObjectAnimator.ofPropertyValuesHolder(drawable, holder);
+        long totalBounceDuration = DRAG_HANDLE_BOUNCE_DURATION_MS + DRAG_HANDLE_BOUNCE_DELAY_MS;
+        // The bounce finishes by this progress, the rest of the duration just delays next bounce.
+        float delayStartProgress = 1f - (float) DRAG_HANDLE_BOUNCE_DELAY_MS / totalBounceDuration;
+        mDragHandleAnim.addUpdateListener((v) -> invalidate(invalidateRegion));
+        mDragHandleAnim.setDuration(totalBounceDuration);
+        mDragHandleAnim.setInterpolator(clampToProgress(LINEAR, 0, delayStartProgress));
+        mDragHandleAnim.setRepeatCount(DRAG_HANDLE_BOUNCE_REPEAT_COUNT);
+        getOverlay().add(drawable);
+
+        mDragHandleAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mDragHandleAnim = null;
+                getOverlay().remove(drawable);
+                updateDragHandleVisibility(drawable);
+            }
+        });
+        mDragHandleAnim.start();
+        return true;
+    }
+
+    private void stopDragHandleEducationAnim() {
+        if (mDragHandleAnim != null) {
+            mDragHandleAnim.end();
+        }
     }
 
     protected void updateDragHandleBounds() {
         DeviceProfile grid = mLauncher.getDeviceProfile();
         final int left;
         final int width = getMeasuredWidth();
-        final int top = getMeasuredHeight() - mDragHandleSize - grid.getInsets().bottom;
+        final int top = getMeasuredHeight() - mDragHandleSize.y - grid.getInsets().bottom;
         final int topMargin;
 
         if (grid.isVerticalBarLayout()) {
-            topMargin = grid.workspacePadding.bottom;
+            topMargin = grid.workspacePadding.bottom + mDragHandlePaddingInVerticalBarLayout;
             if (grid.isSeascape()) {
-                left = width - grid.getInsets().right - mDragHandleSize;
+                left = width - grid.getInsets().right - mDragHandleSize.x
+                        - mDragHandlePaddingInVerticalBarLayout;
             } else {
-                left = mDragHandleSize + grid.getInsets().left;
+                left = grid.getInsets().left + mDragHandlePaddingInVerticalBarLayout;
             }
         } else {
-            left = (width - mDragHandleSize) / 2;
+            left = Math.round((width - mDragHandleSize.x) / 2f);
             topMargin = grid.hotseatBarSizePx;
         }
         mDragHandleBounds.offsetTo(left, top - topMargin);
         mHitRect.set(mDragHandleBounds);
-        float inset = -mDragHandleSize / 2;
-        mHitRect.inset(inset, inset);
+        // Inset outwards to increase touch size.
+        mHitRect.inset((mDragHandleSize.x - mDragHandleTouchSize) / 2f,
+                (mDragHandleSize.y - mDragHandleTouchSize) / 2f);
 
         if (mDragHandle != null) {
             mDragHandle.setBounds(mDragHandleBounds);
@@ -341,7 +392,7 @@
         if (visible != wasVisible) {
             if (visible) {
                 mDragHandle = recycle != null ? recycle :
-                        mLauncher.getDrawable(R.drawable.drag_handle_indicator);
+                        mLauncher.getDrawable(R.drawable.drag_handle_indicator_shadow);
                 mDragHandle.setBounds(mDragHandleBounds);
 
                 updateDragHandleAlpha();
@@ -397,7 +448,7 @@
 
         @Override
         protected int getVirtualViewAt(float x, float y) {
-            return  mDragHandleBounds.contains((int) x, (int) y)
+            return  mHitRect.contains((int) x, (int) y)
                     ? DRAG_HANDLE_ID : INVALID_ID;
         }
 
@@ -470,7 +521,10 @@
         }
     }
 
-    public int getDragHandleSize() {
-        return mDragHandleSize;
+    /**
+     * @return The top of this scrim view, or {@link Float#MAX_VALUE} if there's no distinct top.
+     */
+    public float getVisualTop() {
+        return Float.MAX_VALUE;
     }
 }