Merge changes from topic "all-apps-arrow" into ub-launcher3-master

* changes:
  Add all apps education bounce animation
  Update vertical drag handle (all apps arrow)
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 8944088..b8c4a21 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -210,6 +210,7 @@
             WorkspaceItemInfo info = predictions.get(i);
             PredictedAppIcon icon = PredictedAppIcon.createIcon(mSampleHotseat, info);
             icon.setEnabled(false);
+            icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
             icon.verifyHighRes();
             CellLayout.LayoutParams lp = new CellLayout.LayoutParams(i, 0, 1, 1);
             mSampleHotseat.addViewToCellLayout(icon, i, info.getViewId(), lp, true);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index d3bb4f9..9bc0975 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -349,7 +349,10 @@
                 mHotSeatItemsCount);
     }
 
-    private void pinPrediction(ItemInfo info) {
+    /**
+     * Pins a predicted app icon into place.
+     */
+    public void pinPrediction(ItemInfo info) {
         PredictedAppIcon icon = (PredictedAppIcon) mHotseat.getChildAt(
                 mHotseat.getCellXFromOrder(info.rank),
                 mHotseat.getCellYFromOrder(info.rank));
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 4bbb48c..304c77f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.uioverrides;
 
+import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.PIN_PREDICTION;
 import static com.android.launcher3.graphics.IconShape.getShape;
 
 import android.content.Context;
@@ -26,15 +27,19 @@
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.core.graphics.ColorUtils;
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
@@ -43,7 +48,8 @@
 /**
  * A BubbleTextView with a ring around it's drawable
  */
-public class PredictedAppIcon extends DoubleShadowBubbleTextView {
+public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
+        LauncherAccessibilityDelegate.AccessibilityActionHandler {
 
     private static final float RING_EFFECT_RATIO = 0.11f;
 
@@ -97,6 +103,13 @@
         super.applyFromWorkspaceItem(info);
         int color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
         mIconRingPaint.setColor(ColorUtils.setAlphaComponent(color, 200));
+        if (mIsPinned) {
+            setContentDescription(info.contentDescription);
+        } else {
+            setContentDescription(
+                    getContext().getString(R.string.hotseat_prediction_content_description,
+                            info.contentDescription));
+        }
     }
 
     /**
@@ -104,9 +117,9 @@
      */
     public void pin(WorkspaceItemInfo info) {
         if (mIsPinned) return;
+        mIsPinned = true;
         applyFromWorkspaceItem(info);
         setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
-        mIsPinned = true;
         ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
         invalidate();
     }
@@ -122,6 +135,27 @@
     }
 
     @Override
+    public void addSupportedAccessibilityActions(AccessibilityNodeInfo accessibilityNodeInfo) {
+        accessibilityNodeInfo.addAction(
+                new AccessibilityNodeInfo.AccessibilityAction(PIN_PREDICTION,
+                        getContext().getText(R.string.pin_prediction)));
+    }
+
+    @Override
+    public boolean performAccessibilityAction(int action, ItemInfo info) {
+        QuickstepLauncher launcher = Launcher.cast(Launcher.getLauncher(getContext()));
+        if (action == PIN_PREDICTION) {
+            if (launcher == null || launcher.getHotseatPredictionController() == null) {
+                return false;
+            }
+            HotseatPredictionController controller = launcher.getHotseatPredictionController();
+            controller.pinPrediction(info);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
     public void getIconBounds(Rect outBounds) {
         super.getIconBounds(outBounds);
         if (!mIsPinned && !mIsDrawingDot) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index a6eea0c..da81114 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -19,12 +19,9 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 
-import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
-import android.graphics.Rect;
 import android.os.Bundle;
-import android.view.Gravity;
 import android.view.View;
 
 import androidx.annotation.Nullable;
@@ -38,7 +35,6 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.Folder;
-import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
@@ -71,77 +67,6 @@
      */
     public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
             SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
-    public static final RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
-        @Override
-        public void mapRect(int left, int top, int right, int bottom, Rect out) {
-            out.left = top;
-            out.top = right;
-            out.right = bottom;
-            out.bottom = left;
-        }
-
-        @Override
-        public void mapInsets(Context context, Rect insets, Rect out) {
-            // If there is a display cutout, the top insets in portrait would also include the
-            // cutout, which we will get as the left inset in landscape. Using the max of left and
-            // top allows us to cover both cases (with or without cutout).
-            if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
-                out.top = Math.max(insets.top, insets.left);
-                out.bottom = Math.max(insets.right, insets.bottom);
-                out.left = out.right = 0;
-            } else {
-                out.top = Math.max(insets.top, insets.left);
-                out.bottom = insets.right;
-                out.left = insets.bottom;
-                out.right = 0;
-            }
-        }
-    };
-    public static final RotationMode ROTATION_SEASCAPE = new RotationMode(90) {
-        @Override
-        public void mapRect(int left, int top, int right, int bottom, Rect out) {
-            out.left = bottom;
-            out.top = left;
-            out.right = top;
-            out.bottom = right;
-        }
-
-        @Override
-        public void mapInsets(Context context, Rect insets, Rect out) {
-            if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
-                out.top = Math.max(insets.top, insets.right);
-                out.bottom = Math.max(insets.left, insets.bottom);
-                out.left = out.right = 0;
-            } else {
-                out.top = Math.max(insets.top, insets.right);
-                out.bottom = insets.left;
-                out.right = insets.bottom;
-                out.left = 0;
-            }
-        }
-
-        @Override
-        public int toNaturalGravity(int absoluteGravity) {
-            int horizontalGravity = absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
-            int verticalGravity = absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK;
-
-            if (horizontalGravity == Gravity.RIGHT) {
-                horizontalGravity = Gravity.LEFT;
-            } else if (horizontalGravity == Gravity.LEFT) {
-                horizontalGravity = Gravity.RIGHT;
-            }
-
-            if (verticalGravity == Gravity.TOP) {
-                verticalGravity = Gravity.BOTTOM;
-            } else if (verticalGravity == Gravity.BOTTOM) {
-                verticalGravity = Gravity.TOP;
-            }
-
-            return ((absoluteGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK)
-                    & ~Gravity.VERTICAL_GRAVITY_MASK)
-                    | horizontalGravity | verticalGravity;
-        }
-    };
     private HotseatPredictionController mHotseatPredictionController;
 
     @Override
@@ -153,12 +78,6 @@
     }
 
     @Override
-    protected RotationMode getFakeRotationMode(DeviceProfile dp) {
-        return !dp.isVerticalBarLayout() ? RotationMode.NORMAL
-                : (dp.isSeascape() ? ROTATION_SEASCAPE : ROTATION_LANDSCAPE);
-    }
-
-    @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         onStateOrResumeChanged();
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 131b71f..3d6e519 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.uioverrides;
 
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -91,7 +92,7 @@
 
         View actionsView = mLauncher.getActionsView();
         if (actionsView != null) {
-            propertySetter.setViewAlpha(actionsView, buttonAlpha, actionInterpolator);
+            propertySetter.setFloat(actionsView, VIEW_ALPHA, buttonAlpha, actionInterpolator);
         }
     }
 
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 113cdec..5abeae4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -46,7 +46,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.model.PagedViewOrientedState;
 import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.testing.TestProtocol;
@@ -149,8 +148,8 @@
         VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
     }
 
-    public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode navBarRotationMode) {
-        return mRecentsView != null ? mRecentsView.getEventDispatcher(navBarRotationMode) : null;
+    public Consumer<MotionEvent> getRecentsViewDispatcher(float navbarRotation) {
+        return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null;
     }
 
     @UiThread
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 496a3d8..61fe6cb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -444,9 +444,12 @@
             GestureState newGestureState;
 
             if (mDeviceState.isInSwipeUpTouchRegion(event)) {
+                // Clone the previous gesture state since onConsumerAboutToBeSwitched might trigger
+                // onConsumerInactive and wipe the previous gesture state
+                GestureState prevGestureState = new GestureState(mGestureState);
                 newGestureState = createGestureState();
                 mConsumer.onConsumerAboutToBeSwitched();
-                mConsumer = newConsumer(mGestureState, newGestureState, event);
+                mConsumer = newConsumer(prevGestureState, newGestureState, event);
 
                 ActiveGestureLog.INSTANCE.addLog("setInputConsumer", mConsumer.getType());
                 mUncheckedConsumer = mConsumer;
@@ -686,7 +689,7 @@
      * To be called by the consumer when it's no longer active.
      */
     private void onConsumerInactive(InputConsumer caller) {
-        if (mConsumer == caller) {
+        if (mConsumer != null && mConsumer.isInConsumerHierarchy(caller)) {
             mConsumer = mUncheckedConsumer = mResetGestureInputConsumer;
             mGestureState = new GestureState();
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 71465eb..bcc9707 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -30,6 +30,11 @@
     }
 
     @Override
+    public boolean isInConsumerHierarchy(InputConsumer candidate) {
+        return this == candidate || mDelegate.isInConsumerHierarchy(candidate);
+    }
+
+    @Override
     public boolean allowInterceptByParent() {
         return mDelegate.allowInterceptByParent() && mState != STATE_ACTIVE;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index a462949..fe9ef2b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -182,7 +182,7 @@
         if (mPassedWindowMoveSlop && mInteractionHandler != null
                 && !mRecentsViewDispatcher.hasConsumer()) {
             mRecentsViewDispatcher.setConsumer(mInteractionHandler
-                .getRecentsViewDispatcher(mNavBarPosition.getRotationMode()));
+                    .getRecentsViewDispatcher(mNavBarPosition.getRotation()));
         }
         int edgeFlags = ev.getEdgeFlags();
         ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR);
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 1c95a9e..e3b3102 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
@@ -71,6 +71,7 @@
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.OrientationEventListener;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
@@ -90,6 +91,7 @@
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PendingAnimation.EndState;
@@ -97,9 +99,9 @@
 import com.android.launcher3.anim.SpringProperty;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
@@ -169,6 +171,7 @@
                 }
             };
 
+    private OrientationEventListener mOrientationListener;
     private int mPreviousRotation;
     protected RecentsAnimationController mRecentsAnimationController;
     protected RecentsAnimationTargets mRecentsAnimationTargets;
@@ -376,6 +379,22 @@
 
         // Initialize quickstep specific cache params here, as this is constructed only once
         mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
+
+        mOrientationListener = new OrientationEventListener(getContext()) {
+            @Override
+            public void onOrientationChanged(int i) {
+                int rotation = RotationHelper.getRotationFromDegrees(i);
+                if (mPreviousRotation != rotation) {
+                    animateRecentsRotationInPlace(rotation);
+                    if (rotation == 0) {
+                        showActionsView();
+                    } else {
+                        hideActionsView();
+                    }
+                    mPreviousRotation = rotation;
+                }
+            }
+        };
     }
 
     public OverScroller getScroller() {
@@ -504,6 +523,15 @@
     }
 
     public void setOverviewStateEnabled(boolean enabled) {
+        if (supportsVerticalLandscape()
+                && !TestProtocol.sDisableSensorRotation // Ignore hardware dependency for tests
+                && mOrientationListener.canDetectOrientation()) {
+            if (enabled) {
+                mOrientationListener.enable();
+            } else {
+                mOrientationListener.disable();
+            }
+        }
         mOverviewStateEnabled = enabled;
         updateTaskStackListenerState();
         if (!enabled) {
@@ -784,23 +812,14 @@
         if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
             return;
         }
-        CurveProperties curveProperties = mOrientationHandler
-            .getCurveProperties(this, mInsets);
-        int scroll = curveProperties.scroll;
-        final int halfPageSize = curveProperties.halfPageSize;
-        final int screenCenter = curveProperties.screenCenter;
-        final int halfScreenSize = curveProperties.halfScreenSize;
-        final int pageSpacing = mPageSpacing;
-        mScrollState.scrollFromEdge = mIsRtl ? scroll : (mMaxScroll - scroll);
+        mOrientationHandler.getCurveProperties(this, mInsets, mScrollState);
+        mScrollState.scrollFromEdge =
+                mIsRtl ? mScrollState.scroll : (mMaxScroll - mScrollState.scroll);
 
         final int pageCount = getPageCount();
         for (int i = 0; i < pageCount; i++) {
             View page = getPageAt(i);
-            float pageCenter = mOrientationHandler.getViewCenterPosition(page) + halfPageSize;
-            float distanceFromScreenCenter = screenCenter - pageCenter;
-            float distanceToReachEdge = halfScreenSize + halfPageSize + pageSpacing;
-            mScrollState.linearInterpolation = Math.min(1,
-                    Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
+            mScrollState.updateInterpolation(mOrientationHandler.getChildStart(page), mPageSpacing);
             ((PageCallbacks) page).onPageScroll(mScrollState);
         }
     }
@@ -947,6 +966,35 @@
         setSwipeDownShouldLaunchApp(true);
     }
 
+    private void animateRecentsRotationInPlace(int newRotation) {
+        if (!supportsVerticalLandscape()) {
+            return;
+        }
+
+        AnimatorSet pa = setRecentsChangedOrientation(true);
+        pa.addListener(AnimationSuccessListener.forRunnable(() -> {
+            updateLayoutRotation(newRotation);
+            mActivity.getDragLayer().recreateControllers();
+            rotateAllChildTasks();
+            setRecentsChangedOrientation(false).start();
+        }));
+        pa.start();
+    }
+
+    public AnimatorSet setRecentsChangedOrientation(boolean fadeInChildren) {
+        getRunningTaskIndex();
+        int runningIndex = getCurrentPage();
+        AnimatorSet as = new AnimatorSet();
+        for (int i = 0; i < getTaskViewCount(); i++) {
+            if (runningIndex == i) {
+                continue;
+            }
+            View taskView = getTaskViewAt(i);
+            as.play(ObjectAnimator.ofFloat(taskView, View.ALPHA, fadeInChildren ? 0 : 1));
+        }
+        return as;
+    }
+
     abstract protected boolean supportsVerticalLandscape();
 
     private void rotateAllChildTasks() {
@@ -1143,7 +1191,7 @@
         default void onPageScroll(ScrollState scrollState) {}
     }
 
-    public static class ScrollState {
+    public static class ScrollState extends CurveProperties {
 
         /**
          * The progress from 0 to 1, where 0 is the center
@@ -1155,6 +1203,17 @@
          * The amount by which all the content is scrolled relative to the end of the list.
          */
         public float scrollFromEdge;
+
+        /**
+         * Updates linearInterpolation for the provided child position
+         */
+        public void updateInterpolation(int childStart, int pageSpacing) {
+            float pageCenter = childStart + halfPageSize;
+            float distanceFromScreenCenter = screenCenter - pageCenter;
+            float distanceToReachEdge = halfScreenSize + halfPageSize + pageSpacing;
+            linearInterpolation = Math.min(1,
+                    Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
+        }
     }
 
     public void setIgnoreResetTask(int taskId) {
@@ -1924,13 +1983,13 @@
         return offsetX;
     }
 
-    public Consumer<MotionEvent> getEventDispatcher(RotationMode navBarRotationMode) {
+    public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
         float degreesRotated;
-        if (navBarRotationMode == RotationMode.NORMAL) {
+        if (navbarRotation == 0) {
             degreesRotated = mOrientationState.areMultipleLayoutOrientationsDisabled() ? 0 :
                     RotationHelper.getDegreesFromRotation(mLayoutRotation);
         } else {
-            degreesRotated = -navBarRotationMode.surfaceRotation;
+            degreesRotated = -navbarRotation;
         }
         if (degreesRotated == 0) {
             return super::onTouchEvent;
@@ -1940,7 +1999,7 @@
         // undo that transformation since PagedView also accommodates for the transformation via
         // PagedOrientationHandler
         return e -> {
-            if (navBarRotationMode != RotationMode.NORMAL
+            if (navbarRotation != 0
                     && !mOrientationState.areMultipleLayoutOrientationsDisabled()) {
                 RotationHelper.transformEventForNavBar(e, true);
                 super.onTouchEvent(e);
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 9ef38c0..c6b1477 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -90,6 +90,9 @@
     <!-- tip shown if user declines migration and has some open spots for prediction -->
     <string name="hotseat_tip_gaps_filled">App suggestions added to empty space</string>
 
+    <!-- content description for hotseat items -->
+    <string name="hotseat_prediction_content_description">Predicted app: <xliff:g id="title" example="Chrome">%1$s</xliff:g></string>
+
 
     <!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
     <string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 135daef..abdff0d 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -88,9 +88,7 @@
         super.onCreate(savedInstanceState);
         mSystemActions = new SystemActions(this);
 
-        SysUINavigationMode.Mode mode = SysUINavigationMode.INSTANCE.get(this)
-                .addModeChangeListener(this);
-        getRotationHelper().setRotationHadDifferentUI(mode != Mode.NO_BUTTON);
+        SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this);
 
         if (!getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) {
             getStateManager().addStateListener(new LauncherStateManager.StateListener() {
@@ -141,7 +139,6 @@
     @Override
     public void onNavigationModeChanged(Mode newMode) {
         getDragLayer().recreateControllers();
-        getRotationHelper().setRotationHadDifferentUI(newMode != Mode.NO_BUTTON);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 631df4c..5118906 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -130,6 +130,17 @@
         mGestureId = gestureId;
     }
 
+    public GestureState(GestureState other) {
+        mHomeIntent = other.mHomeIntent;
+        mOverviewIntent = other.mOverviewIntent;
+        mActivityInterface = other.mActivityInterface;
+        mStateCallback = other.mStateCallback;
+        mGestureId = other.mGestureId;
+        mRunningTask = other.mRunningTask;
+        mEndTarget = other.mEndTarget;
+        mFinishingRecentsAnimationTaskId = other.mFinishingRecentsAnimationTaskId;
+    }
+
     public GestureState() {
         // Do nothing, only used for initializing the gesture state prior to user unlock
         mHomeIntent = new Intent();
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 8efaeb9..818d836 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -70,6 +70,13 @@
     }
 
     /**
+     * Returns true if the given input consumer is in the hierarchy of this input consumer.
+     */
+    default boolean isInConsumerHierarchy(InputConsumer candidate) {
+        return this == candidate;
+    }
+
+    /**
      * Called by the event queue when the consumer is about to be switched to a new consumer.
      * Consumers should update the state accordingly here before the state is passed to the new
      * consumer.
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
index 593b695..aeb718d 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
@@ -107,11 +107,11 @@
     }
 
     void onAttachedToWindow() {
-        mEdgeBackGestureHandler.setIsEnabled(true);
+        mEdgeBackGestureHandler.setViewGroupParent((ViewGroup) getRootView());
     }
 
     void onDetachedFromWindow() {
-        mEdgeBackGestureHandler.setIsEnabled(false);
+        mEdgeBackGestureHandler.setViewGroupParent(null);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
index 04cd2f4..f34530e 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
@@ -17,21 +17,18 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.PointF;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.Handler;
-import android.os.Looper;
 import android.os.SystemProperties;
 import android.view.Display;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnTouchListener;
 import android.view.ViewConfiguration;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.ResourceUtils;
 
@@ -40,7 +37,7 @@
  *
  * Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java.
  */
-public class EdgeBackGestureHandler implements DisplayListener, OnTouchListener {
+public class EdgeBackGestureHandler implements OnTouchListener {
 
     private static final String TAG = "EdgeBackGestureHandler";
     private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
@@ -106,29 +103,22 @@
         mEdgeWidth = ResourceUtils.getNavbarSize("config_backGestureInset", res);
     }
 
-    void setIsEnabled(boolean isEnabled) {
-        if (isEnabled == mIsEnabled) {
-            return;
-        }
-        mIsEnabled = isEnabled;
+    void setViewGroupParent(@Nullable ViewGroup parent) {
+        mIsEnabled = parent != null;
 
         if (mEdgeBackPanel != null) {
             mEdgeBackPanel.onDestroy();
             mEdgeBackPanel = null;
         }
 
-        if (!mIsEnabled) {
-            mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
-        } else {
-            updateDisplaySize();
-            mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
-                    new Handler(Looper.getMainLooper()));
-
+        if (mIsEnabled) {
             // Add a nav bar panel window.
-            mEdgeBackPanel = new EdgeBackGesturePanel(mContext);
+            mEdgeBackPanel = new EdgeBackGesturePanel(mContext, parent, createLayoutParams());
             mEdgeBackPanel.setBackCallback(mBackCallback);
-            mEdgeBackPanel.setLayoutParams(createLayoutParams());
-            updateDisplaySize();
+            if (mContext.getDisplay() != null) {
+                mContext.getDisplay().getRealSize(mDisplaySize);
+                mEdgeBackPanel.setDisplaySize(mDisplaySize);
+            }
         }
     }
 
@@ -136,21 +126,11 @@
         mGestureCallback = callback;
     }
 
-    private WindowManager.LayoutParams createLayoutParams() {
+    private LayoutParams createLayoutParams() {
         Resources resources = mContext.getResources();
-        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
+        return new LayoutParams(
                 ResourceUtils.getNavbarSize("navigation_edge_panel_width", resources),
-                ResourceUtils.getNavbarSize("navigation_edge_panel_height", resources),
-                LayoutParams.TYPE_APPLICATION_PANEL,
-                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
-                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
-                PixelFormat.TRANSLUCENT);
-        layoutParams.setTitle(TAG + mDisplayId);
-        layoutParams.windowAnimations = 0;
-        layoutParams.setFitInsetsTypes(0 /* types */);
-        return layoutParams;
+                ResourceUtils.getNavbarSize("navigation_edge_panel_height", resources));
     }
 
     @Override
@@ -232,26 +212,6 @@
         }
     }
 
-    @Override
-    public void onDisplayAdded(int displayId) { }
-
-    @Override
-    public void onDisplayRemoved(int displayId) { }
-
-    @Override
-    public void onDisplayChanged(int displayId) {
-        if (displayId == mDisplayId) {
-            updateDisplaySize();
-        }
-    }
-
-    private void updateDisplaySize() {
-        mContext.getDisplay().getRealSize(mDisplaySize);
-        if (mEdgeBackPanel != null) {
-            mEdgeBackPanel.setDisplaySize(mDisplaySize);
-        }
-    }
-
     void setInsets(int leftInset, int rightInset) {
         mLeftInset = leftInset;
         mRightInset = rightInset;
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
index 34eeafc..5bf5026 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
@@ -26,11 +26,11 @@
 import android.graphics.Path;
 import android.graphics.Point;
 import android.os.SystemClock;
-import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
-import android.view.WindowManager;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
@@ -110,7 +110,6 @@
     private static final Interpolator RUBBER_BAND_INTERPOLATOR_APPEAR =
             new PathInterpolator(1.0f / RUBBER_BAND_AMOUNT_APPEAR, 1.0f, 1.0f, 1.0f);
 
-    private final WindowManager mWindowManager;
     private BackCallback mBackCallback;
 
     /**
@@ -147,7 +146,6 @@
 
     private VelocityTracker mVelocityTracker;
     private int mArrowPaddingEnd;
-    private WindowManager.LayoutParams mLayoutParams;
 
     /**
      * True if the panel is currently on the left of the screen
@@ -232,11 +230,9 @@
                 }
             };
 
-    public EdgeBackGesturePanel(Context context) {
+    public EdgeBackGesturePanel(Context context, ViewGroup parent, LayoutParams layoutParams) {
         super(context);
 
-        mWindowManager = context.getSystemService(WindowManager.class);
-
         mDensity = context.getResources().getDisplayMetrics().density;
 
         mBaseTranslation = dp(BASE_TRANSLATION_DP);
@@ -290,11 +286,15 @@
 
         mSwipeThreshold = ResourceUtils.getDimenByName(
             "navigation_edge_action_drag_threshold", context.getResources(), 16 /* defaultValue */);
+        parent.addView(this, layoutParams);
         setVisibility(GONE);
     }
 
     void onDestroy() {
-        mWindowManager.removeView(this);
+        ViewGroup parent = (ViewGroup) getParent();
+        if (parent != null) {
+            parent.removeView(this);
+        }
     }
 
     @Override
@@ -305,9 +305,6 @@
     @SuppressLint("RtlHardcoded")
     void setIsLeftPanel(boolean isLeftPanel) {
         mIsLeftPanel = isLeftPanel;
-        mLayoutParams.gravity = mIsLeftPanel
-                ? (Gravity.LEFT | Gravity.TOP)
-                : (Gravity.RIGHT | Gravity.TOP);
     }
 
     boolean getIsLeftPanel() {
@@ -323,11 +320,6 @@
         mBackCallback = callback;
     }
 
-    void setLayoutParams(WindowManager.LayoutParams layoutParams) {
-        mLayoutParams = layoutParams;
-        mWindowManager.addView(this, mLayoutParams);
-    }
-
     private float getCurrentAngle() {
         return mCurrentAngle;
     }
@@ -349,7 +341,6 @@
                 mStartY = event.getY();
                 setVisibility(VISIBLE);
                 updatePosition(event.getY());
-                mWindowManager.updateViewLayout(this, mLayoutParams);
                 break;
             case MotionEvent.ACTION_MOVE:
                 handleMoveEvent(event);
@@ -614,10 +605,11 @@
     }
 
     private void updatePosition(float touchY) {
-        float position = touchY - mFingerOffset;
-        position = Math.max(position, mMinArrowPosition);
-        position -= mLayoutParams.height / 2.0f;
-        mLayoutParams.y = MathUtils.clamp((int) position, 0, mDisplaySize.y);
+        float positionY = touchY - mFingerOffset;
+        positionY = Math.max(positionY, mMinArrowPosition);
+        positionY -= getLayoutParams().height / 2.0f;
+        setX(mIsLeftPanel ? 0 : mDisplaySize.x - getLayoutParams().width);
+        setY(MathUtils.clamp((int) positionY, 0, mDisplaySize.y));
     }
 
     private void setDesiredVerticalTransition(float verticalTranslation, boolean animated) {
diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
index 8dc19dc..74e6b29 100644
--- a/quickstep/src/com/android/quickstep/util/NavBarPosition.java
+++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
@@ -17,12 +17,8 @@
 
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 
-import android.content.Context;
-import android.graphics.Rect;
-import android.view.Gravity;
 import android.view.Surface;
 
-import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.util.DefaultDisplay;
 import com.android.quickstep.SysUINavigationMode;
 
@@ -31,79 +27,6 @@
  */
 public class NavBarPosition {
 
-    public static final RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
-        @Override
-        public void mapRect(int left, int top, int right, int bottom, Rect out) {
-            out.left = top;
-            out.top = right;
-            out.right = bottom;
-            out.bottom = left;
-        }
-
-        @Override
-        public void mapInsets(Context context, Rect insets, Rect out) {
-            // If there is a display cutout, the top insets in portrait would also include the
-            // cutout, which we will get as the left inset in landscape. Using the max of left and
-            // top allows us to cover both cases (with or without cutout).
-            if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
-                out.top = Math.max(insets.top, insets.left);
-                out.bottom = Math.max(insets.right, insets.bottom);
-                out.left = out.right = 0;
-            } else {
-                out.top = Math.max(insets.top, insets.left);
-                out.bottom = insets.right;
-                out.left = insets.bottom;
-                out.right = 0;
-            }
-        }
-    };
-
-    public static final RotationMode ROTATION_SEASCAPE = new RotationMode(90) {
-        @Override
-        public void mapRect(int left, int top, int right, int bottom, Rect out) {
-            out.left = bottom;
-            out.top = left;
-            out.right = top;
-            out.bottom = right;
-        }
-
-        @Override
-        public void mapInsets(Context context, Rect insets, Rect out) {
-            if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
-                out.top = Math.max(insets.top, insets.right);
-                out.bottom = Math.max(insets.left, insets.bottom);
-                out.left = out.right = 0;
-            } else {
-                out.top = Math.max(insets.top, insets.right);
-                out.bottom = insets.left;
-                out.right = insets.bottom;
-                out.left = 0;
-            }
-        }
-
-        @Override
-        public int toNaturalGravity(int absoluteGravity) {
-            int horizontalGravity = absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
-            int verticalGravity = absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK;
-
-            if (horizontalGravity == Gravity.RIGHT) {
-                horizontalGravity = Gravity.LEFT;
-            } else if (horizontalGravity == Gravity.LEFT) {
-                horizontalGravity = Gravity.RIGHT;
-            }
-
-            if (verticalGravity == Gravity.TOP) {
-                verticalGravity = Gravity.BOTTOM;
-            } else if (verticalGravity == Gravity.BOTTOM) {
-                verticalGravity = Gravity.TOP;
-            }
-
-            return ((absoluteGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK)
-                    & ~Gravity.VERTICAL_GRAVITY_MASK)
-                    | horizontalGravity | verticalGravity;
-        }
-    };
-
     private final SysUINavigationMode.Mode mMode;
     private final int mDisplayRotation;
 
@@ -120,8 +43,7 @@
         return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270;
     }
 
-    public RotationMode getRotationMode() {
-        return isLeftEdge() ? ROTATION_SEASCAPE
-                : (isRightEdge() ? ROTATION_LANDSCAPE : RotationMode.NORMAL);
+    public float getRotation() {
+        return isLeftEdge() ? 90 : (isRightEdge() ? -90 : 0);
     }
 }
diff --git a/res/values/config.xml b/res/values/config.xml
index 1675a98..ef67613 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -109,6 +109,7 @@
     <item type="id" name="action_dismiss_notification" />
     <item type="id" name="action_remote_action_shortcut" />
     <item type="id" name="action_dismiss_prediction" />
+    <item type="id" name="action_pin_prediction"/>
 
     <!-- QSB IDs. DO not change -->
     <item type="id" name="search_container_workspace" />
diff --git a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
index f8ac010..7bc34cf 100644
--- a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -30,11 +30,8 @@
 
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.LauncherLayoutBuilder;
 import com.android.launcher3.util.LauncherModelHelper;
 import com.android.launcher3.util.LauncherRoboTestRunner;
@@ -46,8 +43,6 @@
 import org.robolectric.annotation.LooperMode;
 import org.robolectric.annotation.LooperMode.Mode;
 
-import java.util.ArrayList;
-
 /**
  * Tests for layout parser for remote layout
  */
@@ -120,18 +115,6 @@
     }
 
     private void writeLayoutAndLoad(LauncherLayoutBuilder builder) throws Exception {
-        mModelHelper.setupDefaultLayoutProvider(builder);
-
-        LoaderResults results = new LoaderResults(
-                LauncherAppState.getInstance(mTargetContext),
-                mModelHelper.getBgDataModel(),
-                mModelHelper.getAllAppsList(),
-                new Callbacks[0]);
-        LoaderTask task = new LoaderTask(
-                LauncherAppState.getInstance(mTargetContext),
-                mModelHelper.getAllAppsList(),
-                mModelHelper.getBgDataModel(),
-                results);
-        Executors.MODEL_EXECUTOR.submit(() -> task.loadWorkspace(new ArrayList<>())).get();
+        mModelHelper.setupDefaultLayoutProvider(builder).loadModelSync();
     }
 }
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
index f16ed33..76cb747 100644
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
@@ -127,6 +127,10 @@
     @Override
     protected List<LauncherActivityInfo> getShortcutConfigActivityList(String packageName,
             UserHandle user) {
-        return Collections.emptyList();
+        Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName);
+        return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
+                .stream()
+                .map(ri -> getLauncherActivityInfo(ri.activityInfo))
+                .collect(Collectors.toList());
     }
 }
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowTypeface.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowTypeface.java
new file mode 100644
index 0000000..0e7c1de
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowTypeface.java
@@ -0,0 +1,38 @@
+/*
+ * 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.shadows;
+
+import android.graphics.Typeface;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowTypeface;
+
+/**
+ * Extension of {@link ShadowTypeface} with missing shadow methods
+ */
+@Implements(Typeface.class)
+public class LShadowTypeface extends ShadowTypeface {
+
+    @Implementation
+    public static Typeface create(Typeface family, int weight, boolean italic) {
+        int style = italic ? Typeface.ITALIC : Typeface.NORMAL;
+        if (weight >= 400) {
+            style |= Typeface.BOLD;
+        }
+        return create(family, style);
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowWallpaperManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowWallpaperManager.java
new file mode 100644
index 0000000..d60251c
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowWallpaperManager.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 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.shadows;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowUserManager;
+import org.robolectric.shadows.ShadowWallpaperManager;
+
+/**
+ * Extension of {@link ShadowUserManager} with missing shadow methods
+ */
+@Implements(WallpaperManager.class)
+public class LShadowWallpaperManager extends ShadowWallpaperManager {
+
+    @Implementation
+    protected static WallpaperManager getInstance(Context context) {
+        return context.getSystemService(WallpaperManager.class);
+    }
+
+    /**
+     * Remove this once the fix for
+     * https://github.com/robolectric/robolectric/issues/5285
+     * is available
+     */
+    public static void initializeMock() {
+        WallpaperManager wm = mock(WallpaperManager.class);
+        ShadowApplication shadowApplication = Shadows.shadowOf(RuntimeEnvironment.application);
+        shadowApplication.setSystemService(Context.WALLPAPER_SERVICE, wm);
+        doReturn(0).when(wm).getDesiredMinimumWidth();
+        doReturn(0).when(wm).getDesiredMinimumHeight();
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowOverrides.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowOverrides.java
new file mode 100644
index 0000000..131f691
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/ShadowOverrides.java
@@ -0,0 +1,61 @@
+/*
+ * 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.shadows;
+
+import android.content.Context;
+
+import com.android.launcher3.util.MainThreadInitializedObject.ObjectProvider;
+import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.launcher3.util.ResourceBasedOverride.Overrides;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Shadow for {@link Overrides} to provide custom overrides for test
+ */
+@Implements(value = Overrides.class, isInAndroidSdk = false)
+public class ShadowOverrides {
+
+    private static Map<Class, ObjectProvider> sProviderMap = new HashMap<>();
+
+    @Implementation
+    public static <T extends ResourceBasedOverride> T getObject(
+            Class<T> clazz, Context context, int resId) {
+        ObjectProvider<T> provider = sProviderMap.get(clazz);
+        if (provider != null) {
+            return provider.get(context);
+        }
+        return Shadow.directlyOn(Overrides.class, "getObject",
+                ClassParameter.from(Class.class, clazz),
+                ClassParameter.from(Context.class, context),
+                ClassParameter.from(int.class, resId));
+    }
+
+    public static <T> void setProvider(Class<T> clazz, ObjectProvider<T> provider) {
+        sProviderMap.put(clazz, provider);
+    }
+
+    public static void clearProvider() {
+        sProviderMap.clear();
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
new file mode 100644
index 0000000..209bae0
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.ui;
+
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.Mockito.mock;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.MotionEvent.PointerProperties;
+import android.view.View;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.FolderPagedView;
+import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.shadows.ShadowOverrides;
+import com.android.launcher3.util.LauncherLayoutBuilder;
+import com.android.launcher3.util.LauncherLayoutBuilder.FolderBuilder;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.LauncherRoboTestRunner;
+import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.widget.WidgetsFullSheet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
+import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.util.ReflectionHelpers;
+
+/**
+ * Tests scroll behavior at various Launcher UI components
+ */
+@RunWith(LauncherRoboTestRunner.class)
+@LooperMode(Mode.PAUSED)
+public class LauncherUIScrollTest {
+
+    private Context mTargetContext;
+    private InvariantDeviceProfile mIdp;
+    private LauncherModelHelper mModelHelper;
+
+    private LauncherLayoutBuilder mLayoutBuilder;
+
+    @Before
+    public void setup() throws Exception {
+        mModelHelper = new LauncherModelHelper();
+        mTargetContext = RuntimeEnvironment.application;
+        mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
+        ShadowOverrides.setProvider(UserEventDispatcher.class,
+                c -> mock(UserEventDispatcher.class));
+
+        Settings.Global.putFloat(mTargetContext.getContentResolver(),
+                Settings.Global.WINDOW_ANIMATION_SCALE, 0);
+
+        mModelHelper.installApp(TEST_PACKAGE);
+        // LayoutBuilder with 3 workspace pages
+        mLayoutBuilder = new LauncherLayoutBuilder()
+                .atWorkspace(0,  mIdp.numRows - 1, 0).putApp(TEST_PACKAGE, TEST_PACKAGE)
+                .atWorkspace(0,  mIdp.numRows - 1, 1).putApp(TEST_PACKAGE, TEST_PACKAGE)
+                .atWorkspace(0,  mIdp.numRows - 1, 2).putApp(TEST_PACKAGE, TEST_PACKAGE);
+    }
+
+    @Test
+    public void testWorkspacePagesBound() throws Exception {
+        // Verify that the workspace if bound synchronously
+        Launcher launcher = loadLauncher();
+        assertEquals(3, launcher.getWorkspace().getPageCount());
+        assertEquals(0, launcher.getWorkspace().getCurrentPage());
+
+        launcher.dispatchGenericMotionEvent(createScrollEvent(-1));
+        assertNotEquals("Workspace was not scrolled",
+                0, launcher.getWorkspace().getNextPage());
+    }
+
+    @Test
+    public void testAllAppsScroll() throws Exception {
+        // Install 100 apps
+        for (int i = 0; i < 100; i++) {
+            mModelHelper.installApp(TEST_PACKAGE + i);
+        }
+
+        // Bind and open all-apps
+        Launcher launcher = loadLauncher();
+        launcher.getStateManager().goToState(LauncherState.ALL_APPS, false);
+        doLayout(launcher);
+
+        int currentScroll = launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
+        launcher.dispatchGenericMotionEvent(createScrollEvent(-1));
+        int newScroll = launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
+
+        assertNotEquals("All Apps was not scrolled", currentScroll, newScroll);
+        assertEquals("Workspace was scrolled", 0, launcher.getWorkspace().getNextPage());
+    }
+
+    @Test
+    public void testWidgetsListScroll() throws Exception {
+        // Install 100 widgets
+        for (int i = 0; i < 100; i++) {
+            mModelHelper.installCustomShortcut(TEST_PACKAGE + i, "shortcutProvider");
+        }
+
+        // Bind and open widgets
+        Launcher launcher = loadLauncher();
+        WidgetsFullSheet widgets = WidgetsFullSheet.show(launcher, false);
+        doLayout(launcher);
+
+        int currentScroll = widgets.getRecyclerView().getCurrentScrollY();
+        launcher.dispatchGenericMotionEvent(createScrollEvent(-1));
+        int newScroll = widgets.getRecyclerView().getCurrentScrollY();
+        assertNotEquals("Widgets was not scrolled", currentScroll, newScroll);
+        assertEquals("Workspace was scrolled", 0, launcher.getWorkspace().getNextPage());
+    }
+
+    @Test
+    public void testFolderPageScroll() throws Exception {
+        // Add a folder with multiple icons
+        FolderBuilder fb = mLayoutBuilder.atWorkspace(mIdp.numColumns / 2, mIdp.numRows / 2, 0)
+                .putFolder(0);
+        for (int i = 0; i < 100; i++) {
+            fb.addApp(TEST_PACKAGE, TEST_PACKAGE);
+        }
+
+        // Bind and open folder
+        Launcher launcher = loadLauncher();
+        doLayout(launcher);
+        launcher.getWorkspace().getFirstMatch((i, v) -> v instanceof FolderIcon).performClick();
+        ShadowLooper.idleMainLooper();
+        doLayout(launcher);
+        FolderPagedView folderPages = Folder.getOpen(launcher).getContent();
+
+        assertEquals(0, folderPages.getNextPage());
+        launcher.dispatchGenericMotionEvent(createScrollEvent(-1));
+        assertNotEquals("Folder page was not scrolled", 0, folderPages.getNextPage());
+        assertEquals("Workspace was scrolled", 0, launcher.getWorkspace().getNextPage());
+    }
+
+    private Launcher loadLauncher() throws Exception {
+        mModelHelper.setupDefaultLayoutProvider(mLayoutBuilder).loadModelSync();
+
+        Launcher launcher = Robolectric.buildActivity(Launcher.class).setup().get();
+        doLayout(launcher);
+        ViewOnDrawExecutor executor = ReflectionHelpers.getField(launcher, "mPendingExecutor");
+        if (executor != null) {
+            executor.runAllTasks();
+        }
+        return launcher;
+    }
+
+    private static void doLayout(Activity activity) {
+        DeviceProfile dp = InvariantDeviceProfile.INSTANCE
+                .get(RuntimeEnvironment.application).portraitProfile;
+        View view = activity.getWindow().getDecorView();
+        view.measure(makeMeasureSpec(dp.widthPx, EXACTLY), makeMeasureSpec(dp.heightPx, EXACTLY));
+        view.layout(0, 0, dp.widthPx, dp.heightPx);
+        ShadowLooper.idleMainLooper();
+    }
+
+    private static MotionEvent createScrollEvent(int scroll) {
+        DeviceProfile dp = InvariantDeviceProfile.INSTANCE
+                .get(RuntimeEnvironment.application).portraitProfile;
+
+        final PointerProperties[] pointerProperties = new PointerProperties[1];
+        pointerProperties[0] = new PointerProperties();
+        pointerProperties[0].id = 0;
+        final MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[1];
+        coords[0] = new MotionEvent.PointerCoords();
+        coords[0].setAxisValue(MotionEvent.AXIS_VSCROLL, scroll);
+        coords[0].x = dp.widthPx / 2;
+        coords[0].y = dp.heightPx / 2;
+
+        final long time = SystemClock.uptimeMillis();
+        return MotionEvent.obtain(time, time, MotionEvent.ACTION_SCROLL, 1,
+                pointerProperties, coords, 0, 0, 1.0f, 1.0f, 0, 0,
+                InputDevice.SOURCE_CLASS_POINTER, 0);
+    }
+
+}
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
index 20b1453..d593d84 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.util;
 
+import static android.content.Intent.ACTION_CREATE_SHORTCUT;
+
 import static com.android.launcher3.LauncherSettings.Favorites.CONTENT_URI;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
@@ -44,6 +46,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.pm.UserCache;
 
 import org.mockito.ArgumentCaptor;
@@ -61,6 +64,7 @@
 import java.lang.reflect.Field;
 import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.function.Function;
 
@@ -345,7 +349,8 @@
     /**
      * Sets up a dummy provider to load the provided layout by default, next time the layout loads
      */
-    public void setupDefaultLayoutProvider(LauncherLayoutBuilder builder) throws Exception {
+    public LauncherModelHelper setupDefaultLayoutProvider(LauncherLayoutBuilder builder)
+            throws Exception {
         Context context = RuntimeEnvironment.application;
         InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
         idp.numRows = idp.numColumns = idp.numHotseatIcons = DEFAULT_GRID_SIZE;
@@ -363,23 +368,53 @@
         Uri layoutUri = LauncherProvider.getLayoutUri(TEST_PROVIDER_AUTHORITY, context);
         shadowOf(context.getContentResolver()).registerInputStream(layoutUri,
                 new ByteArrayInputStream(bos.toByteArray()));
+        return this;
     }
 
     /**
      * Simulates an apk install with a default main activity with same class and package name
      */
     public void installApp(String component) throws NameNotFoundException {
-        ShadowPackageManager spm = shadowOf(RuntimeEnvironment.application.getPackageManager());
-        ComponentName cn = new ComponentName(component, component);
-        spm.addActivityIfNotPresent(cn);
-
         IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
         filter.addCategory(Intent.CATEGORY_LAUNCHER);
+        installApp(component, component, filter);
+    }
+
+    /**
+     * Simulates a custom shortcut install
+     */
+    public void installCustomShortcut(String pkg, String clazz) throws NameNotFoundException {
+        installApp(pkg, clazz, new IntentFilter(ACTION_CREATE_SHORTCUT));
+    }
+
+    private void installApp(String pkg, String clazz, IntentFilter filter)
+            throws NameNotFoundException {
+        ShadowPackageManager spm = shadowOf(RuntimeEnvironment.application.getPackageManager());
+        ComponentName cn = new ComponentName(pkg, clazz);
+        spm.addActivityIfNotPresent(cn);
+
         filter.addCategory(Intent.CATEGORY_DEFAULT);
         spm.addIntentFilterForActivity(cn, filter);
     }
 
     /**
+     * Loads the model in memory synchronously
+     */
+    public void loadModelSync() throws ExecutionException, InterruptedException {
+        // Since robolectric tests run on main thread, we run the loader-UI calls on a temp thread,
+        // so that we can wait appropriately for the loader to complete.
+        ReflectionHelpers.setField(getModel(), "mMainExecutor", Executors.UI_HELPER_EXECUTOR);
+
+        Callbacks mockCb = mock(Callbacks.class);
+        getModel().addCallbacksAndLoad(mockCb);
+
+        Executors.MODEL_EXECUTOR.submit(() -> { }).get();
+        Executors.UI_HELPER_EXECUTOR.submit(() -> { }).get();
+        ReflectionHelpers.setField(getModel(), "mMainExecutor", Executors.MAIN_EXECUTOR);
+        getModel().removeCallbacks(mockCb);
+    }
+
+    /**
      * An extension of LauncherProvider backed up by in-memory database.
      */
     public static class TestLauncherProvider extends LauncherProvider {
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java b/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java
index 6277c66..744b478b 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java
@@ -21,10 +21,13 @@
 import com.android.launcher3.shadows.LShadowBackupManager;
 import com.android.launcher3.shadows.LShadowBitmap;
 import com.android.launcher3.shadows.LShadowLauncherApps;
+import com.android.launcher3.shadows.LShadowTypeface;
 import com.android.launcher3.shadows.LShadowUserManager;
+import com.android.launcher3.shadows.LShadowWallpaperManager;
 import com.android.launcher3.shadows.ShadowDeviceFlag;
 import com.android.launcher3.shadows.ShadowLooperExecutor;
 import com.android.launcher3.shadows.ShadowMainThreadInitializedObject;
+import com.android.launcher3.shadows.ShadowOverrides;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 
 import org.junit.runners.model.InitializationError;
@@ -49,9 +52,12 @@
             LShadowLauncherApps.class,
             LShadowBitmap.class,
             LShadowBackupManager.class,
+            LShadowTypeface.class,
+            LShadowWallpaperManager.class,
             ShadowLooperExecutor.class,
             ShadowMainThreadInitializedObject.class,
             ShadowDeviceFlag.class,
+            ShadowOverrides.class
     };
 
     public LauncherRoboTestRunner(Class<?> testClass) throws InitializationError {
@@ -78,6 +84,9 @@
 
             // Disable plugins
             PluginManagerWrapper.INSTANCE.initializeForTesting(mock(PluginManagerWrapper.class));
+
+            // Initialize mock wallpaper manager
+            LShadowWallpaperManager.initializeMock();
         }
 
         @Override
@@ -86,6 +95,7 @@
 
             ShadowLog.stream = null;
             ShadowMainThreadInitializedObject.resetInitializedObjects();
+            ShadowOverrides.clearProvider();
         }
     }
 }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index c8e73ba..921e8ac 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -151,27 +151,25 @@
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.BubbleTextView, defStyle, 0);
         mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
+        DeviceProfile grid = mActivity.getDeviceProfile();
 
         mDisplay = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
         final int defaultIconSize;
         if (mDisplay == DISPLAY_WORKSPACE) {
-            DeviceProfile grid = mActivity.getWallpaperDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
             setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
             defaultIconSize = grid.iconSizePx;
         } else if (mDisplay == DISPLAY_ALL_APPS) {
-            DeviceProfile grid = mActivity.getDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
             setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
             defaultIconSize = grid.allAppsIconSizePx;
         } else if (mDisplay == DISPLAY_FOLDER) {
-            DeviceProfile grid = mActivity.getDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
             setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
             defaultIconSize = grid.folderChildIconSizePx;
         } else {
             // widget_selection or shortcut_popup
-            defaultIconSize = mActivity.getDeviceProfile().iconSizePx;
+            defaultIconSize = grid.iconSizePx;
         }
 
         mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 9682d09..4259196 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -59,14 +59,12 @@
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.DragPreviewProvider;
-import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.util.CellAndSpan;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.ParcelableSparseArray;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.views.Transposable;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
 import java.lang.annotation.Retention;
@@ -77,7 +75,7 @@
 import java.util.Comparator;
 import java.util.Stack;
 
-public class CellLayout extends ViewGroup implements Transposable {
+public class CellLayout extends ViewGroup {
     private static final String TAG = "CellLayout";
     private static final boolean LOGD = false;
 
@@ -184,7 +182,6 @@
 
     // Related to accessible drag and drop
     private boolean mUseTouchHelper = false;
-    private RotationMode mRotationMode = RotationMode.NORMAL;
 
     public CellLayout(Context context) {
         this(context, null);
@@ -206,7 +203,7 @@
         setClipToPadding(false);
         mActivity = ActivityContext.lookupContext(context);
 
-        DeviceProfile grid = mActivity.getWallpaperDeviceProfile();
+        DeviceProfile grid = mActivity.getDeviceProfile();
 
         mCellWidth = mCellHeight = -1;
         mFixedCellWidth = mFixedCellHeight = -1;
@@ -314,24 +311,6 @@
         }
     }
 
-    public void setRotationMode(RotationMode mode) {
-        if (mRotationMode != mode) {
-            mRotationMode = mode;
-            requestLayout();
-        }
-    }
-
-    @Override
-    public RotationMode getRotationMode() {
-        return mRotationMode;
-    }
-
-    @Override
-    public void setPadding(int left, int top, int right, int bottom) {
-        mRotationMode.mapRect(left, top, right, bottom, mTempRect);
-        super.setPadding(mTempRect.left, mTempRect.top, mTempRect.right, mTempRect.bottom);
-    }
-
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (mUseTouchHelper ||
@@ -789,13 +768,6 @@
         int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
         int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
 
-        mShortcutsAndWidgets.setRotation(mRotationMode.surfaceRotation);
-        if (mRotationMode.isTransposed) {
-            int tmp = childWidthSize;
-            childWidthSize = childHeightSize;
-            childHeightSize = tmp;
-        }
-
         if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
             int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
             int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
@@ -846,15 +818,7 @@
                 right + mTempRect.right + getPaddingRight(),
                 bottom + mTempRect.bottom + getPaddingBottom());
 
-        if (mRotationMode.isTransposed) {
-            int halfW = mShortcutsAndWidgets.getMeasuredWidth() / 2;
-            int halfH = mShortcutsAndWidgets.getMeasuredHeight() / 2;
-            int cX = (left + right) / 2;
-            int cY = (top + bottom) / 2;
-            mShortcutsAndWidgets.layout(cX - halfW, cY - halfH, cX + halfW, cY + halfH);
-        } else {
-            mShortcutsAndWidgets.layout(left, top, right, bottom);
-        }
+        mShortcutsAndWidgets.layout(left, top, right, bottom);
     }
 
     /**
@@ -863,8 +827,7 @@
      * width in {@link DeviceProfile#calculateCellWidth(int, int)}.
      */
     public int getUnusedHorizontalSpace() {
-        return (mRotationMode.isTransposed ? getMeasuredHeight() : getMeasuredWidth())
-                - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
+        return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
     }
 
     public Drawable getScrimBackground() {
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 423f2bb..6f0ebd2 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -67,7 +67,7 @@
     public boolean supportsAccessibilityDrop(ItemInfo info, View view) {
         if (info instanceof WorkspaceItemInfo) {
             // Support the action unless the item is in a context menu.
-            return info.screenId >= 0;
+            return canRemove(info);
         }
 
         return (info instanceof LauncherAppWidgetInfo)
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 76cfe1c..78bd2ff 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -27,15 +27,13 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
-import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.views.Transposable;
 
 import java.util.ArrayList;
 
-public class Hotseat extends CellLayout implements LogContainerProvider, Insettable, Transposable {
+public class Hotseat extends CellLayout implements LogContainerProvider, Insettable {
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mHasVerticalHotseat;
@@ -89,7 +87,7 @@
     @Override
     public void setInsets(Rect insets) {
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
-        DeviceProfile grid = mActivity.getWallpaperDeviceProfile();
+        DeviceProfile grid = mActivity.getDeviceProfile();
         insets = grid.getInsets();
 
         if (grid.isVerticalBarLayout()) {
@@ -117,9 +115,4 @@
     public boolean onTouchEvent(MotionEvent event) {
         return event.getY() > getCellHeight();
     }
-
-    @Override
-    public RotationMode getRotationMode() {
-        return Launcher.getLauncher(getContext()).getRotationMode();
-    }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index fb8bd45..043ea2f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -56,7 +56,6 @@
 import android.content.res.Configuration;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Point;
-import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -102,7 +101,6 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.keyboard.CustomActionsPopup;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
@@ -317,9 +315,6 @@
 
     private float mCurrentAssistantVisibility = 0f;
 
-    private DeviceProfile mStableDeviceProfile;
-    private RotationMode mRotationMode = RotationMode.NORMAL;
-
     protected LauncherOverlayManager mOverlayManager;
     // If true, overlay callbacks are deferred
     private boolean mDeferOverlayCallbacks;
@@ -503,24 +498,12 @@
         super.onConfigurationChanged(newConfig);
     }
 
-    public void reload() {
-        onIdpChanged(mDeviceProfile.inv);
-    }
-
-    private boolean supportsFakeLandscapeUI() {
-        return FeatureFlags.FAKE_LANDSCAPE_UI.get() && !mRotationHelper.homeScreenCanRotate();
-    }
-
     @Override
     public void reapplyUi() {
         reapplyUi(true /* cancelCurrentAnimation */);
     }
 
     public void reapplyUi(boolean cancelCurrentAnimation) {
-        if (supportsFakeLandscapeUI()) {
-            mRotationMode = mStableDeviceProfile == null
-                    ? RotationMode.NORMAL : getFakeRotationMode(mDeviceProfile);
-        }
         getRootView().dispatchInsets();
         getStateManager().reapplyState(cancelCurrentAnimation);
     }
@@ -533,7 +516,6 @@
     private void onIdpChanged(InvariantDeviceProfile idp) {
         mUserEventDispatcher = null;
 
-        DeviceProfile oldWallpaperProfile = getWallpaperDeviceProfile();
         initDeviceProfile(idp);
         dispatchDeviceProfileChanged();
         reapplyUi();
@@ -542,9 +524,7 @@
         // Calling onSaveInstanceState ensures that static cache used by listWidgets is
         // initialized properly.
         onSaveInstanceState(new Bundle());
-        if (oldWallpaperProfile != getWallpaperDeviceProfile()) {
-            mModel.rebindCallbacks();
-        }
+        mModel.rebindCallbacks();
     }
 
     public void onAssistantVisibilityChanged(float visibility) {
@@ -571,41 +551,8 @@
             mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
         }
 
-        if (supportsFakeLandscapeUI() && mDeviceProfile.isVerticalBarLayout()) {
-            mStableDeviceProfile = mDeviceProfile.inv.portraitProfile;
-            mRotationMode = getFakeRotationMode(mDeviceProfile);
-        } else {
-            mStableDeviceProfile = null;
-            mRotationMode = RotationMode.NORMAL;
-        }
-
-        mRotationHelper.updateRotationAnimation();
         onDeviceProfileInitiated();
-        mModelWriter = mModel.getWriter(getWallpaperDeviceProfile().isVerticalBarLayout(), true);
-    }
-
-    public void updateInsets(Rect insets) {
-        mDeviceProfile.updateInsets(insets);
-        if (mStableDeviceProfile != null) {
-            Rect r = mStableDeviceProfile.getInsets();
-            mRotationMode.mapInsets(this, insets, r);
-            mStableDeviceProfile.updateInsets(r);
-        }
-    }
-
-    @Override
-    public RotationMode getRotationMode() {
-        return mRotationMode;
-    }
-
-    /**
-     * Device profile to be used by UI elements which are shown directly on top of the wallpaper
-     * and whose presentation is tied to the wallpaper (and physical device) and not the activity
-     * configuration.
-     */
-    @Override
-    public DeviceProfile getWallpaperDeviceProfile() {
-        return mStableDeviceProfile == null ? mDeviceProfile : mStableDeviceProfile;
+        mModelWriter = mModel.getWriter(getDeviceProfile().isVerticalBarLayout(), true);
     }
 
     public RotationHelper getRotationHelper() {
@@ -1757,7 +1704,6 @@
     public FolderIcon addFolder(CellLayout layout, int container, final int screenId, int cellX,
             int cellY) {
         final FolderInfo folderInfo = new FolderInfo();
-        folderInfo.title = "";
 
         // Update the model
         getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY);
@@ -2053,7 +1999,7 @@
         mAppWidgetHost.clearViews();
 
         if (mHotseat != null) {
-            mHotseat.resetLayout(getWallpaperDeviceProfile().isVerticalBarLayout());
+            mHotseat.resetLayout(getDeviceProfile().isVerticalBarLayout());
         }
         TraceHelper.INSTANCE.endSection(traceToken);
     }
@@ -2728,10 +2674,6 @@
         return new TouchController[] {getDragController(), new AllAppsSwipeController(this)};
     }
 
-    protected RotationMode getFakeRotationMode(DeviceProfile deviceProfile) {
-        return RotationMode.NORMAL;
-    }
-
     protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() {
         return new ScaleAndTranslation(1.1f, 0f, 0f);
     }
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 2b2224a..b4fbbc3 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -83,7 +83,7 @@
                 UI_STATE_ROOT_VIEW, drawInsetBar ? FLAG_DARK_NAV : 0);
 
         // Update device profile before notifying th children.
-        mLauncher.updateInsets(insets);
+        mLauncher.getDeviceProfile().updateInsets(insets);
         boolean resetState = !insets.equals(mInsets);
         setInsets(insets);
 
@@ -127,7 +127,7 @@
     }
 
     public void dispatchInsets() {
-        mLauncher.updateInsets(mInsets);
+        mLauncher.getDeviceProfile().updateInsets(mInsets);
         super.setInsets(mInsets);
     }
 
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 7d7739e..5e47e2f 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -16,6 +16,14 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
+import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR;
+import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE;
+import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_BY;
+import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO;
+
 import android.animation.LayoutTransition;
 import android.animation.TimeInterpolator;
 import android.annotation.SuppressLint;
@@ -42,14 +50,6 @@
 import android.view.animation.Interpolator;
 import android.widget.ScrollView;
 
-import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
-import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
-import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR;
-import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE;
-import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_BY;
-import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO;
-
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.anim.Interpolators;
@@ -63,6 +63,7 @@
 import com.android.launcher3.touch.PortraitPagedViewHandler;
 import com.android.launcher3.util.OverScroller;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.ActivityContext;
 
 import java.util.ArrayList;
 
@@ -1369,10 +1370,6 @@
         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
             switch (event.getAction()) {
                 case MotionEvent.ACTION_SCROLL: {
-                    Launcher launcher = Launcher.getLauncher(getContext());
-                    if (launcher != null) {
-                        AbstractFloatingView.closeAllOpenViews(launcher);
-                    }
                     // Handle mouse (or ext. device) by shifting the page depending on the scroll
                     final float vscroll;
                     final float hscroll;
@@ -1383,8 +1380,8 @@
                         vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
                         hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
                     }
-                    if (Math.abs(vscroll) > Math.abs(hscroll) && !isVerticalScrollable()) {
-                        return true;
+                    if (!canScroll(Math.abs(vscroll), Math.abs(hscroll))) {
+                        return false;
                     }
                     if (hscroll != 0 || vscroll != 0) {
                         boolean isForwardScroll = mIsRtl ? (hscroll < 0 || vscroll < 0)
@@ -1402,8 +1399,13 @@
         return super.onGenericMotionEvent(event);
     }
 
-    protected boolean isVerticalScrollable() {
-        return true;
+    /**
+     * Returns true if the paged view can scroll for the provided vertical and horizontal
+     * scroll values
+     */
+    protected boolean canScroll(float absVScroll, float absHScroll) {
+        ActivityContext ac = ActivityContext.lookupContext(getContext());
+        return (ac == null || AbstractFloatingView.getTopOpenView(ac) == null);
     }
 
     private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index c07dd9d..6326b7a 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -93,7 +93,7 @@
     public void setupLp(View child) {
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
         if (child instanceof LauncherAppWidgetHostView) {
-            DeviceProfile profile = mActivity.getWallpaperDeviceProfile();
+            DeviceProfile profile = mActivity.getDeviceProfile();
             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
                     profile.appWidgetScale.x, profile.appWidgetScale.y);
         } else {
@@ -108,12 +108,12 @@
 
     public int getCellContentHeight() {
         return Math.min(getMeasuredHeight(),
-                mActivity.getWallpaperDeviceProfile().getCellHeight(mContainerType));
+                mActivity.getDeviceProfile().getCellHeight(mContainerType));
     }
 
     public void measureChild(View child) {
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
-        final DeviceProfile profile = mActivity.getWallpaperDeviceProfile();
+        final DeviceProfile profile = mActivity.getDeviceProfile();
 
         if (child instanceof LauncherAppWidgetHostView) {
             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 122b393..0cd08d4 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -62,7 +62,6 @@
 
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
-import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.graphics.TintedDrawableSpan;
 import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.icons.LauncherIcons;
@@ -72,7 +71,6 @@
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.views.Transposable;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
 import java.lang.reflect.Method;
@@ -165,7 +163,7 @@
     public static float getDescendantCoordRelativeToAncestor(
             View descendant, View ancestor, float[] coord, boolean includeRootScroll) {
         return getDescendantCoordRelativeToAncestor(descendant, ancestor, coord, includeRootScroll,
-                false, null);
+                false);
     }
 
     /**
@@ -178,15 +176,12 @@
      * @param includeRootScroll Whether or not to account for the scroll of the descendant:
      *          sometimes this is relevant as in a child's coordinates within the descendant.
      * @param ignoreTransform If true, view transform is ignored
-     * @param outRotation If not null, and {@param ignoreTransform} is true, this is set to the
-     *                   overall rotation of the view in degrees.
      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
      *         assumption fails, we will need to return a pair of scale factors.
      */
     public static float getDescendantCoordRelativeToAncestor(View descendant, View ancestor,
-            float[] coord, boolean includeRootScroll, boolean ignoreTransform,
-            float[] outRotation) {
+            float[] coord, boolean includeRootScroll, boolean ignoreTransform) {
         float scale = 1.0f;
         View v = descendant;
         while(v != ancestor && v != null) {
@@ -196,19 +191,7 @@
                 offsetPoints(coord, -v.getScrollX(), -v.getScrollY());
             }
 
-            if (ignoreTransform) {
-                if (v instanceof Transposable) {
-                    RotationMode m = ((Transposable) v).getRotationMode();
-                    if (m.isTransposed) {
-                        sMatrix.setRotate(m.surfaceRotation, v.getPivotX(), v.getPivotY());
-                        sMatrix.mapPoints(coord);
-
-                        if (outRotation != null) {
-                            outRotation[0] += m.surfaceRotation;
-                        }
-                    }
-                }
-            } else {
+            if (!ignoreTransform) {
                 v.getMatrix().mapPoints(coord);
             }
             offsetPoints(coord, v.getLeft(), v.getTop());
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 190ec16..3a3de26 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -75,7 +75,6 @@
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.graphics.PreloadIconDrawable;
-import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
@@ -276,20 +275,13 @@
     @Override
     public void setInsets(Rect insets) {
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        DeviceProfile stableGrid = mLauncher.getWallpaperDeviceProfile();
 
-        mMaxDistanceForFolderCreation = stableGrid.isTablet
-                ? 0.75f * stableGrid.iconSizePx
-                : 0.55f * stableGrid.iconSizePx;
+        mMaxDistanceForFolderCreation = grid.isTablet
+                ? 0.75f * grid.iconSizePx : 0.55f * grid.iconSizePx;
         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
 
-        Rect padding = stableGrid.workspacePadding;
-
-        RotationMode rotationMode = mLauncher.getRotationMode();
-
-        rotationMode.mapRect(padding, mTempRect);
-        setPadding(mTempRect.left, mTempRect.top, mTempRect.right, mTempRect.bottom);
-        rotationMode.mapRect(stableGrid.getInsets(), mInsets);
+        Rect padding = grid.workspacePadding;
+        setPadding(padding.left, padding.top, padding.right, padding.bottom);
 
         if (mWorkspaceFadeInAdjacentScreens) {
             // In landscape mode the page spacing is set to the default.
@@ -302,12 +294,11 @@
         }
 
 
-        int paddingLeftRight = stableGrid.cellLayoutPaddingLeftRightPx;
-        int paddingBottom = stableGrid.cellLayoutBottomPaddingPx;
+        int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
+        int paddingBottom = grid.cellLayoutBottomPaddingPx;
         for (int i = mWorkspaceScreens.size() - 1; i >= 0; i--) {
-            CellLayout page = mWorkspaceScreens.valueAt(i);
-            page.setRotationMode(rotationMode);
-            page.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
+            mWorkspaceScreens.valueAt(i)
+                    .setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
         }
     }
 
@@ -327,7 +318,7 @@
 
             float scale = 1;
             if (isWidget) {
-                DeviceProfile profile = mLauncher.getWallpaperDeviceProfile();
+                DeviceProfile profile = mLauncher.getDeviceProfile();
                 scale = Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
             }
             size[0] = r.width();
@@ -555,10 +546,9 @@
         // created CellLayout.
         CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
                         R.layout.workspace_screen, this, false /* attachToRoot */);
-        DeviceProfile grid = mLauncher.getWallpaperDeviceProfile();
+        DeviceProfile grid = mLauncher.getDeviceProfile();
         int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
         int paddingBottom = grid.cellLayoutBottomPaddingPx;
-        newScreen.setRotationMode(mLauncher.getRotationMode());
         newScreen.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
 
         mWorkspaceScreens.put(screenId, newScreen);
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index c521c34..c4c4377 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -104,10 +104,8 @@
             Interpolator scaleInterpolator = config.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
             propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
 
-            if (!hotseat.getRotationMode().isTransposed) {
-                setPivotToScaleWithWorkspace(hotseat);
-                setPivotToScaleWithWorkspace(qsbScaleView);
-            }
+            setPivotToScaleWithWorkspace(hotseat);
+            setPivotToScaleWithWorkspace(qsbScaleView);
             float hotseatScale = hotseatScaleAndTranslation.scale;
             Interpolator hotseatScaleInterpolator = config.getInterpolator(ANIM_HOTSEAT_SCALE,
                     scaleInterpolator);
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index f45c0b1..414abab 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -56,6 +56,7 @@
     public static final int REMOVE = R.id.action_remove;
     public static final int UNINSTALL = R.id.action_uninstall;
     public static final int DISMISS_PREDICTION = R.id.action_dismiss_prediction;
+    public static final int PIN_PREDICTION = R.id.action_pin_prediction;
     public static final int RECONFIGURE = R.id.action_reconfigure;
     protected static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
     protected static final int MOVE = R.id.action_move;
@@ -120,6 +121,10 @@
         if (!(host.getTag() instanceof ItemInfo)) return;
         ItemInfo item = (ItemInfo) host.getTag();
 
+        if (host instanceof AccessibilityActionHandler) {
+            ((AccessibilityActionHandler) host).addSupportedAccessibilityActions(info);
+        }
+
         // If the request came from keyboard, do not add custom shortcuts as that is already
         // exposed as a direct shortcut
         if (!fromKeyboard && ShortcutUtil.supportsShortcuts(item)) {
@@ -164,7 +169,7 @@
     private boolean itemSupportsAccessibleDrag(ItemInfo item) {
         if (item instanceof WorkspaceItemInfo) {
             // Support the action unless the item is in a context menu.
-            return item.screenId >= 0;
+            return item.screenId >= 0 && item.container != Favorites.CONTAINER_HOTSEAT_PREDICTION;
         }
         return (item instanceof LauncherAppWidgetInfo)
                 || (item instanceof FolderInfo);
@@ -195,7 +200,10 @@
                 }
             }
         }
-
+        if (host instanceof AccessibilityActionHandler
+                && ((AccessibilityActionHandler) host).performAccessibilityAction(action, item)) {
+            return true;
+        }
         if (action == MOVE) {
             beginAccessibleDrag(host, item);
         } else if (action == ADD_TO_WORKSPACE) {
@@ -466,4 +474,20 @@
         }
         return screenId;
     }
+
+    /**
+     * An interface allowing views to handle their own action.
+     */
+    public interface AccessibilityActionHandler {
+
+        /**
+         * performs accessibility action and returns true on success
+         */
+        boolean performAccessibilityAction(int action, ItemInfo itemInfo);
+
+        /**
+         * adds all the accessibility actions that can be handled.
+         */
+        void addSupportedAccessibilityActions(AccessibilityNodeInfo accessibilityNodeInfo);
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java
index ab4cb6b..f640c3e 100644
--- a/src/com/android/launcher3/allapps/AllAppsPagedView.java
+++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java
@@ -83,7 +83,7 @@
     }
 
     @Override
-    protected boolean isVerticalScrollable() {
-        return false;
+    protected boolean canScroll(float absVScroll, float absHScroll) {
+        return (absHScroll > absVScroll) && super.canScroll(absVScroll, absHScroll);
     }
 }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 92f5112..ec34350 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -95,9 +95,6 @@
     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 FAKE_LANDSCAPE_UI = getDebugFlag(
-            "FAKE_LANDSCAPE_UI", false, "Rotate launcher UI instead of using transposed layout");
-
     public static final BooleanFlag FOLDER_NAME_SUGGEST = new DeviceFlag(
             "FOLDER_NAME_SUGGEST", true,
             "Suggests folder names instead of blank text.");
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 9ece3d3..970c5a0 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -17,10 +17,6 @@
 
 package com.android.launcher3.dragndrop;
 
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.getMode;
-import static android.view.View.MeasureSpec.getSize;
-
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 
@@ -34,14 +30,12 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.util.AttributeSet;
-import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Interpolator;
-import android.widget.FrameLayout;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.CellLayout;
@@ -52,12 +46,10 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.OverviewScrim;
-import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.launcher3.views.Transposable;
 
 import java.util.ArrayList;
 
@@ -560,145 +552,4 @@
     public OverviewScrim getOverviewScrim() {
         return mOverviewScrim;
     }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        RotationMode rotation = mActivity.getRotationMode();
-        int count = getChildCount();
-
-        if (!rotation.isTransposed
-                || getMode(widthMeasureSpec) != EXACTLY
-                || getMode(heightMeasureSpec) != EXACTLY) {
-
-            for (int i = 0; i < count; i++) {
-                final View child = getChildAt(i);
-                child.setRotation(rotation.surfaceRotation);
-            }
-            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        } else {
-
-            for (int i = 0; i < count; i++) {
-                final View child = getChildAt(i);
-                if (child.getVisibility() == GONE) {
-                    continue;
-                }
-                if (!(child instanceof Transposable)) {
-                    measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
-                } else {
-                    measureChildWithMargins(child, heightMeasureSpec, 0, widthMeasureSpec, 0);
-
-                    child.setPivotX(child.getMeasuredWidth() / 2);
-                    child.setPivotY(child.getMeasuredHeight() / 2);
-                    child.setRotation(rotation.surfaceRotation);
-                }
-            }
-            setMeasuredDimension(getSize(widthMeasureSpec), getSize(heightMeasureSpec));
-        }
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        RotationMode rotation = mActivity.getRotationMode();
-        if (!rotation.isTransposed) {
-            super.onLayout(changed, left, top, right, bottom);
-            return;
-        }
-
-        final int count = getChildCount();
-
-        final int parentWidth = right - left;
-        final int parentHeight = bottom - top;
-
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            if (child.getVisibility() == GONE) {
-                continue;
-            }
-
-            final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams();
-
-            if (lp instanceof LayoutParams) {
-                final LayoutParams dlp = (LayoutParams) lp;
-                if (dlp.customPosition) {
-                    child.layout(dlp.x, dlp.y, dlp.x + dlp.width, dlp.y + dlp.height);
-                    continue;
-                }
-            }
-
-            final int width = child.getMeasuredWidth();
-            final int height = child.getMeasuredHeight();
-
-            int childLeft;
-            int childTop;
-
-            int gravity = lp.gravity;
-            if (gravity == -1) {
-                gravity = Gravity.TOP | Gravity.START;
-            }
-
-            final int layoutDirection = getLayoutDirection();
-
-            int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
-
-            if (child instanceof Transposable) {
-                absoluteGravity = rotation.toNaturalGravity(absoluteGravity);
-
-                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
-                    case Gravity.CENTER_HORIZONTAL:
-                        childTop = (parentHeight - height) / 2 +
-                                lp.topMargin - lp.bottomMargin;
-                        break;
-                    case Gravity.RIGHT:
-                        childTop = width / 2 + lp.rightMargin - height / 2;
-                        break;
-                    case Gravity.LEFT:
-                    default:
-                        childTop = parentHeight - lp.leftMargin - width / 2 - height / 2;
-                }
-
-                switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
-                    case Gravity.CENTER_VERTICAL:
-                        childLeft = (parentWidth - width) / 2 +
-                                lp.leftMargin - lp.rightMargin;
-                        break;
-                    case Gravity.BOTTOM:
-                        childLeft = parentWidth - width / 2 - height / 2 - lp.bottomMargin;
-                        break;
-                    case Gravity.TOP:
-                    default:
-                        childLeft = height / 2 - width / 2 + lp.topMargin;
-                }
-            } else {
-                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
-                    case Gravity.CENTER_HORIZONTAL:
-                        childLeft = (parentWidth - width) / 2 +
-                                lp.leftMargin - lp.rightMargin;
-                        break;
-                    case Gravity.RIGHT:
-                        childLeft = parentWidth - width - lp.rightMargin;
-                        break;
-                    case Gravity.LEFT:
-                    default:
-                        childLeft = lp.leftMargin;
-                }
-
-                switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
-                    case Gravity.TOP:
-                        childTop = lp.topMargin;
-                        break;
-                    case Gravity.CENTER_VERTICAL:
-                        childTop = (parentHeight - height) / 2 +
-                                lp.topMargin - lp.bottomMargin;
-                        break;
-                    case Gravity.BOTTOM:
-                        childTop = parentHeight - height - lp.bottomMargin;
-                        break;
-                    default:
-                        childTop = lp.topMargin;
-                }
-            }
-
-            child.layout(childLeft, childTop, childLeft + width, childTop + height);
-        }
-    }
 }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 365e76f..202836d 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -437,7 +437,7 @@
         }
         mItemsInvalidated = true;
         mInfo.addListener(this);
-        mPreviousLabel = mInfo.title.toString();
+        Optional.ofNullable(mInfo.title).ifPresent(title -> mPreviousLabel = title.toString());
         mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
 
         if (!isEmpty(mInfo.title)) {
@@ -1648,6 +1648,10 @@
         }
     }
 
+    public FolderPagedView getContent() {
+        return mContent;
+    }
+
     private void logEditFolderLabel() {
         LauncherEvent launcherEvent = LauncherEvent.newBuilder()
                 .setAction(Action.newBuilder().setType(Action.Type.SOFT_KEYBOARD))
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index b83609e..3d72b49 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -115,6 +115,7 @@
      */
     public AnimatorSet getAnimator() {
         final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams();
+        mFolderIcon.getPreviewItemManager().recomputePreviewDrawingParams();
         ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
         final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(0);
 
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 8251d68..eda9545 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -172,7 +172,7 @@
                     "is dependent on this");
         }
 
-        DeviceProfile grid = activity.getWallpaperDeviceProfile();
+        DeviceProfile grid = activity.getDeviceProfile();
         FolderIcon icon = (FolderIcon) LayoutInflater.from(group.getContext())
                 .inflate(resId, group, false);
 
@@ -570,8 +570,7 @@
     public void drawDot(Canvas canvas) {
         if (!mForceHideDot && ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0)) {
             Rect iconBounds = mDotParams.iconBounds;
-            BubbleTextView.getIconBounds(this, iconBounds,
-                    mActivity.getWallpaperDeviceProfile().iconSizePx);
+            BubbleTextView.getIconBounds(this, iconBounds, mActivity.getDeviceProfile().iconSizePx);
             float iconScale = (float) mBackground.previewSize / iconBounds.width();
             Utilities.scaleRectAboutCenter(iconBounds, iconScale);
 
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index c6d62f8..dcd0e14 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.folder;
 
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_FOLDER;
+
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -27,6 +30,7 @@
 import android.view.View;
 import android.view.ViewDebug;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
@@ -258,7 +262,7 @@
     @Override
     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
         super.onScrollChanged(l, t, oldl, oldt);
-        mPageIndicator.setScroll(l, mMaxScroll);
+        if (mMaxScroll > 0) mPageIndicator.setScroll(l, mMaxScroll);
     }
 
     /**
@@ -614,6 +618,12 @@
         }
     }
 
+    @Override
+    protected boolean canScroll(float absVScroll, float absHScroll) {
+        return AbstractFloatingView.getTopOpenViewWithType(mFolder.mLauncher,
+                TYPE_ALL & ~TYPE_FOLDER) == null;
+    }
+
     public int itemsPerPage() {
         return mOrganizer.getMaxItemsPerPage();
     }
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index 2d177d2..27b906b 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -153,7 +153,7 @@
         mBgColor = ta.getColor(R.styleable.FolderIconPreview_folderFillColor, 0);
         ta.recycle();
 
-        DeviceProfile grid = activity.getWallpaperDeviceProfile();
+        DeviceProfile grid = activity.getDeviceProfile();
         previewSize = grid.folderIconSizePx;
 
         basePreviewOffsetX = (availableSpaceX - previewSize) / 2;
diff --git a/src/com/android/launcher3/graphics/RotationMode.java b/src/com/android/launcher3/graphics/RotationMode.java
deleted file mode 100644
index 6dd356a..0000000
--- a/src/com/android/launcher3/graphics/RotationMode.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.content.Context;
-import android.graphics.Rect;
-
-public abstract class RotationMode {
-
-    public static final RotationMode NORMAL = new RotationMode(0) { };
-
-    public final float surfaceRotation;
-    public final boolean isTransposed;
-
-    public RotationMode(float surfaceRotation) {
-        this.surfaceRotation = surfaceRotation;
-        isTransposed = surfaceRotation != 0;
-    }
-
-    public final void mapRect(Rect rect, Rect out) {
-        mapRect(rect.left, rect.top, rect.right, rect.bottom, out);
-    }
-
-    public void mapRect(int left, int top, int right, int bottom, Rect out) {
-        out.set(left, top, right, bottom);
-    }
-
-    public void mapInsets(Context context, Rect insets, Rect out) {
-        out.set(insets);
-    }
-
-    public int toNaturalGravity(int absoluteGravity) {
-        return absoluteGravity;
-    }
-}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 62904ae..fc0997b 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -45,8 +45,6 @@
 import android.util.MutableInt;
 import android.util.TimingLogger;
 
-import androidx.annotation.VisibleForTesting;
-
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InstallShortcutReceiver;
@@ -282,8 +280,7 @@
         this.notify();
     }
 
-    @VisibleForTesting
-    void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
+    private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
         loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI);
     }
 
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index d28fcf6..8bb6a08 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -26,6 +26,7 @@
 import static com.android.launcher3.config.FeatureFlags.FLAG_ENABLE_FIXED_ROTATION_TRANSFORM;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
+import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
@@ -36,12 +37,9 @@
 import android.provider.Settings;
 import android.view.MotionEvent;
 import android.view.Surface;
-import android.view.WindowManager;
 
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.UiThreadHelper;
 
 import java.util.ArrayList;
@@ -77,7 +75,7 @@
     public static final int REQUEST_ROTATE = 1;
     public static final int REQUEST_LOCK = 2;
 
-    private final Launcher mLauncher;
+    private final Activity mActivity;
     private final SharedPreferences mSharedPrefs;
     private final SharedPreferences mFeatureFlagsPrefs;
 
@@ -104,17 +102,16 @@
     // This is used to defer setting rotation flags until the activity is being created
     private boolean mInitialized;
     private boolean mDestroyed;
-    private boolean mRotationHasDifferentUI;
 
     private int mLastActivityFlags = -1;
 
-    public RotationHelper(Launcher launcher) {
-        mLauncher = launcher;
+    public RotationHelper(Activity activity) {
+        mActivity = activity;
 
         // On large devices we do not handle auto-rotate differently.
-        mIgnoreAutoRotateSettings = mLauncher.getResources().getBoolean(R.bool.allow_rotation);
+        mIgnoreAutoRotateSettings = mActivity.getResources().getBoolean(R.bool.allow_rotation);
         if (!mIgnoreAutoRotateSettings) {
-            mSharedPrefs = Utilities.getPrefs(mLauncher);
+            mSharedPrefs = Utilities.getPrefs(mActivity);
             mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
             mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
                     getAllowRotationDefaultValue());
@@ -122,8 +119,8 @@
             mSharedPrefs = null;
         }
 
-        mContentResolver = launcher.getContentResolver();
-        mFeatureFlagsPrefs = Utilities.getFeatureFlagsPrefs(mLauncher);
+        mContentResolver = activity.getContentResolver();
+        mFeatureFlagsPrefs = Utilities.getFeatureFlagsPrefs(mActivity);
         mFeatureFlagsPrefs.registerOnSharedPreferenceChangeListener(this);
         updateForcedRotation(true);
     }
@@ -144,7 +141,7 @@
             mForcedRotation = isForcedRotation;
         }
         UI_HELPER_EXECUTOR.execute(() -> {
-            if (mLauncher.checkSelfPermission(WRITE_SECURE_SETTINGS) == PERMISSION_GRANTED) {
+            if (mActivity.checkSelfPermission(WRITE_SECURE_SETTINGS) == PERMISSION_GRANTED) {
                 Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME,
                             mForcedRotation ? 1 : 0);
             }
@@ -165,29 +162,6 @@
         mForcedRotationChangedListeners.remove(listener);
     }
 
-    public void setRotationHadDifferentUI(boolean rotationHasDifferentUI) {
-        mRotationHasDifferentUI = rotationHasDifferentUI;
-    }
-
-    public boolean homeScreenCanRotate() {
-        return mRotationHasDifferentUI || mIgnoreAutoRotateSettings || mAutoRotateEnabled
-                || mStateHandlerRequest != REQUEST_NONE
-                || mLauncher.getDeviceProfile().isMultiWindowMode;
-    }
-
-    public void updateRotationAnimation() {
-        if (FeatureFlags.FAKE_LANDSCAPE_UI.get()) {
-            WindowManager.LayoutParams lp = mLauncher.getWindow().getAttributes();
-            int oldAnim = lp.rotationAnimation;
-            lp.rotationAnimation = homeScreenCanRotate()
-                    ? WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE
-                    : WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
-            if (oldAnim != lp.rotationAnimation) {
-                mLauncher.getWindow().setAttributes(lp);
-            }
-        }
-    }
-
     @Override
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
         if (FLAG_ENABLE_FIXED_ROTATION_TRANSFORM.equals(s)) {
@@ -199,17 +173,13 @@
         mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
                 getAllowRotationDefaultValue());
         if (mAutoRotateEnabled != wasRotationEnabled) {
-
             notifyChange();
-            updateRotationAnimation();
-            mLauncher.reapplyUi();
         }
     }
 
     public void setStateHandlerRequest(int request) {
         if (mStateHandlerRequest != request) {
             mStateHandlerRequest = request;
-            updateRotationAnimation();
             notifyChange();
         }
     }
@@ -231,7 +201,7 @@
     // Used by tests only.
     public void forceAllowRotationForTesting(boolean allowRotation) {
         mIgnoreAutoRotateSettings =
-                allowRotation || mLauncher.getResources().getBoolean(R.bool.allow_rotation);
+                allowRotation || mActivity.getResources().getBoolean(R.bool.allow_rotation);
         // TODO(b/150214193) Tests currently expect launcher to be able to be rotated
         //   Modify tests for this new behavior
         mForcedRotation = !allowRotation;
@@ -243,7 +213,6 @@
         if (!mInitialized) {
             mInitialized = true;
             notifyChange();
-            updateRotationAnimation();
         }
     }
 
@@ -285,7 +254,7 @@
         }
         if (activityFlags != mLastActivityFlags) {
             mLastActivityFlags = activityFlags;
-            UiThreadHelper.setOrientationAsync(mLauncher, activityFlags);
+            UiThreadHelper.setOrientationAsync(mActivity, activityFlags);
         }
     }
 
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 4e49c6e..e786f07 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -184,6 +184,10 @@
                         mDeviceProfile.allAppsCellHeightPx);
                 break;
             }
+
+            case TestProtocol.REQUEST_MOCK_SENSOR_ROTATION:
+                TestProtocol.sDisableSensorRotation = true;
+                break;
         }
         return response;
     }
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 9f20df6..3181752 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -92,6 +92,9 @@
 
     public static final String REQUEST_OVERVIEW_ACTIONS_ENABLED = "overview-actions-enabled";
 
+    public static boolean sDisableSensorRotation;
+    public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
+
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
 
     public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 1db65b9..6715bc1 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -16,8 +16,11 @@
 
 package com.android.launcher3.touch;
 
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+
 import android.content.res.Resources;
-import android.graphics.Matrix;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -32,13 +35,8 @@
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.util.OverScroller;
 
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
-
 public class LandscapePagedViewHandler implements PagedOrientationHandler {
 
     @Override
@@ -67,12 +65,11 @@
     }
 
     @Override
-    public CurveProperties getCurveProperties(PagedView pagedView, Rect mInsets) {
-        int scroll = pagedView.getScrollY();
-        final int halfPageSize = pagedView.getNormalChildHeight() / 2;
-        final int screenCenter = mInsets.top + pagedView.getPaddingTop() + scroll + halfPageSize;
-        final int halfScreenSize = pagedView.getMeasuredHeight() / 2;
-        return new CurveProperties(scroll, halfPageSize, screenCenter, halfScreenSize);
+    public void getCurveProperties(PagedView view, Rect mInsets, CurveProperties out) {
+        out.scroll = view.getScrollY();
+        out.halfPageSize = view.getNormalChildHeight() / 2;
+        out.halfScreenSize = view.getMeasuredHeight() / 2;
+        out.screenCenter = mInsets.top + view.getPaddingTop() + out.scroll + out.halfPageSize;
     }
 
     @Override
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index b4802cd..24fa815 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -83,7 +83,7 @@
     void delegateScrollTo(PagedView pagedView, int primaryScroll);
     void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y);
     void scrollerStartScroll(OverScroller scroller, int newPosition);
-    CurveProperties getCurveProperties(PagedView pagedView, Rect insets);
+    void getCurveProperties(PagedView view, Rect mInsets, CurveProperties out);
     boolean isGoingUp(float displacement);
 
     /**
@@ -92,18 +92,12 @@
      */
     void adjustFloatingIconStartVelocity(PointF velocity);
 
-    class CurveProperties {
-        public final int scroll;
-        public final int halfPageSize;
-        public final int screenCenter;
-        public final int halfScreenSize;
 
-        public CurveProperties(int scroll, int halfPageSize, int screenCenter, int halfScreenSize) {
-            this.scroll = scroll;
-            this.halfPageSize = halfPageSize;
-            this.screenCenter = screenCenter;
-            this.halfScreenSize = halfScreenSize;
-        }
+    class CurveProperties {
+        public int scroll;
+        public int halfPageSize;
+        public int screenCenter;
+        public int halfScreenSize;
     }
 
     class ChildBounds {
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 22eee49..6d903b3 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -16,8 +16,11 @@
 
 package com.android.launcher3.touch;
 
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
+
 import android.content.res.Resources;
-import android.graphics.Matrix;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -32,13 +35,8 @@
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.util.OverScroller;
 
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
-
 public class PortraitPagedViewHandler implements PagedOrientationHandler {
 
     @Override
@@ -67,12 +65,11 @@
     }
 
     @Override
-    public CurveProperties getCurveProperties(PagedView pagedView, Rect mInsets) {
-        int scroll = pagedView.getScrollX();
-        final int halfPageSize = pagedView.getNormalChildWidth() / 2;
-        final int screenCenter = mInsets.left + pagedView.getPaddingLeft() + scroll + halfPageSize;
-        final int halfScreenSize = pagedView.getMeasuredWidth() / 2;
-        return new CurveProperties(scroll, halfPageSize, screenCenter, halfScreenSize);
+    public void getCurveProperties(PagedView view, Rect mInsets, CurveProperties out) {
+        out.scroll = view.getScrollX();
+        out.halfPageSize = view.getNormalChildWidth() / 2;
+        out.halfScreenSize = view.getMeasuredWidth() / 2;
+        out.screenCenter = mInsets.left + view.getPaddingLeft() + out.scroll + out.halfPageSize;
     }
 
     @Override
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 0331a86..c9cdeff 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -22,7 +22,6 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.dot.DotInfo;
 
 /**
@@ -57,19 +56,6 @@
 
     DeviceProfile getDeviceProfile();
 
-    /**
-     * Device profile to be used by UI elements which are shown directly on top of the wallpaper
-     * and whose presentation is tied to the wallpaper (and physical device) and not the activity
-     * configuration.
-     */
-    default DeviceProfile getWallpaperDeviceProfile() {
-        return getDeviceProfile();
-    }
-
-    default RotationMode getRotationMode() {
-        return RotationMode.NORMAL;
-    }
-
     static ActivityContext lookupContext(Context context) {
         if (context instanceof ActivityContext) {
             return (ActivityContext) context;
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 3e2560f..ad8d69d 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -92,8 +92,6 @@
     private ClipIconView mClipIconView;
     private @Nullable Drawable mBadge;
 
-    private float mRotation;
-
     private View mOriginalIcon;
     private RectF mPositionOut;
     private Runnable mOnTargetChangeRunnable;
@@ -194,18 +192,17 @@
      * @param positionOut Rect that will hold the size and position of v.
      */
     private void matchPositionOf(Launcher launcher, View v, boolean isOpening, RectF positionOut) {
-        float rotation = getLocationBoundsForView(launcher, v, isOpening, positionOut);
+        getLocationBoundsForView(launcher, v, isOpening, positionOut);
         final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams(
                 Math.round(positionOut.width()),
                 Math.round(positionOut.height()));
-        updatePosition(rotation, positionOut, lp);
+        updatePosition(positionOut, lp);
         setLayoutParams(lp);
 
         mClipIconView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
     }
 
-    private void updatePosition(float rotation, RectF pos, InsettableFrameLayout.LayoutParams lp) {
-        mRotation = rotation;
+    private void updatePosition(RectF pos, InsettableFrameLayout.LayoutParams lp) {
         mPositionOut.set(pos);
         lp.ignoreInsets = true;
         // Position the floating view exactly on top of the original
@@ -228,7 +225,7 @@
      * - For DeepShortcutView, we return the bounds of the icon view.
      * - For BubbleTextView, we return the icon bounds.
      */
-    private static float getLocationBoundsForView(Launcher launcher, View v, boolean isOpening,
+    private static void getLocationBoundsForView(Launcher launcher, View v, boolean isOpening,
             RectF outRect) {
         boolean ignoreTransform = !isOpening;
         if (v instanceof DeepShortcutView) {
@@ -239,7 +236,7 @@
             ignoreTransform = false;
         }
         if (v == null) {
-            return 0;
+            return;
         }
 
         Rect iconBounds = new Rect();
@@ -253,15 +250,13 @@
 
         float[] points = new float[] {iconBounds.left, iconBounds.top, iconBounds.right,
                 iconBounds.bottom};
-        float[] rotation = new float[] {0};
         Utilities.getDescendantCoordRelativeToAncestor(v, launcher.getDragLayer(), points,
-                false, ignoreTransform, rotation);
+                false, ignoreTransform);
         outRect.set(
                 Math.min(points[0], points[2]),
                 Math.min(points[1], points[3]),
                 Math.max(points[0], points[2]),
                 Math.max(points[1], points[3]));
-        return rotation[0];
     }
 
     /**
@@ -443,14 +438,10 @@
 
     @Override
     protected void dispatchDraw(Canvas canvas) {
-        int count = canvas.save();
-        canvas.rotate(mRotation,
-                mFinalDrawableBounds.exactCenterX(), mFinalDrawableBounds.exactCenterY());
         super.dispatchDraw(canvas);
         if (mBadge != null) {
             mBadge.draw(canvas);
         }
-        canvas.restoreToCount(count);
     }
 
     public void fastFinish() {
@@ -487,11 +478,10 @@
     @Override
     public void onGlobalLayout() {
         if (mOriginalIcon.isAttachedToWindow() && mPositionOut != null) {
-            float rotation = getLocationBoundsForView(mLauncher, mOriginalIcon, mIsOpening,
+            getLocationBoundsForView(mLauncher, mOriginalIcon, mIsOpening,
                     sTmpRectF);
-            if (rotation != mRotation || !sTmpRectF.equals(mPositionOut)) {
-                updatePosition(rotation, sTmpRectF,
-                        (InsettableFrameLayout.LayoutParams) getLayoutParams());
+            if (!sTmpRectF.equals(mPositionOut)) {
+                updatePosition(sTmpRectF, (InsettableFrameLayout.LayoutParams) getLayoutParams());
                 if (mOnTargetChangeRunnable != null) {
                     mOnTargetChangeRunnable.run();
                 }
diff --git a/src/com/android/launcher3/views/Transposable.java b/src/com/android/launcher3/views/Transposable.java
deleted file mode 100644
index 929c1aa..0000000
--- a/src/com/android/launcher3/views/Transposable.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * Copyright (C) 2019 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 com.android.launcher3.graphics.RotationMode;
-
-/**
- * Indicates that a view can be transposed.
- */
-public interface Transposable {
-
-    RotationMode getRotationMode();
-}
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index aaebedd..b3e9734 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -98,6 +98,11 @@
         onWidgetsBound();
     }
 
+    @VisibleForTesting
+    public WidgetsRecyclerView getRecyclerView() {
+        return mRecyclerView;
+    }
+
     @Override
     protected Pair<View, String> getAccessibilityTarget() {
         return Pair.create(mRecyclerView, getContext().getString(
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index c15557b..7ec6214 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -19,11 +19,14 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 
 import com.android.launcher3.BaseRecyclerView;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestProtocol;
 
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
@@ -155,18 +158,66 @@
                     mScrollbar.isHitInParent(e.getX(), e.getY(), mFastScrollerOffset);
         }
         if (mTouchDownOnScroller) {
-            return mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
+            final boolean result = mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
+            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+                Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onInterceptTouchEvent 1 " + result);
+            }
+            return result;
+        }
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onInterceptTouchEvent 2 false");
         }
         return false;
     }
 
     @Override
     public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsRecyclerView.onTouchEvent");
+        }
         if (mTouchDownOnScroller) {
             mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
         }
     }
 
     @Override
-    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { }
+    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onRequestDisallowInterceptTouchEvent "
+                    + disallowIntercept);
+        }
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        final boolean result = super.dispatchTouchEvent(ev);
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsRecyclerView: state: "
+                    + getScrollState()
+                    + " can scroll: " + getLayoutManager().canScrollVertically()
+                    + " result: " + result
+                    + " layout suppressed: " + isLayoutSuppressed()
+                    + " event: " + ev);
+        }
+        return result;
+    }
+
+    @Override
+    public void stopNestedScroll() {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "stopNestedScroll");
+        }
+        super.stopNestedScroll();
+    }
+
+    @Override
+    public void setLayoutFrozen(boolean frozen) {
+        if (frozen != isLayoutSuppressed()) {
+            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+                Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "setLayoutFrozen " + frozen
+                        + " @ " + android.util.Log.getStackTraceString(new Throwable()));
+            }
+        }
+        super.setLayoutFrozen(frozen);
+    }
 }
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 7cd656e..873f1cb 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -162,6 +162,7 @@
         mLauncher.enableDebugTracing();
         // Avoid double-reporting of Launcher crashes.
         mLauncher.setOnLauncherCrashed(() -> mLauncherPid = 0);
+        mLauncher.disableSensorRotation();
     }
 
     protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
@@ -277,6 +278,7 @@
             clearPackageData(mDevice.getLauncherPackageName());
             mLauncher.enableDebugTracing();
             mLauncherPid = mLauncher.getPid();
+            mLauncher.disableSensorRotation();
         }
     }
 
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index a3c70ec..001a88f 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -275,8 +275,10 @@
     }
 
     private void verifyPendingWidgetPresent() {
+        final Widget widget = mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT);
+        if (widget == null) mLauncher.dumpViewHierarchy(); // b/152645831
         assertTrue("Pending widget is not present",
-                mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT) != null);
+                widget != null);
     }
 
     /**
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 4a2d699..808be66 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -43,7 +43,7 @@
     AllApps(LauncherInstrumentation launcher) {
         super(launcher);
         final UiObject2 allAppsContainer = verifyActiveContainer();
-        mHeight = allAppsContainer.getVisibleBounds().height();
+        mHeight = mLauncher.getVisibleBounds(allAppsContainer).height();
         final UiObject2 appListRecycler = mLauncher.waitForObjectInContainer(allAppsContainer,
                 "apps_list_view");
         // Wait for the recycler to populate.
@@ -66,7 +66,7 @@
             LauncherInstrumentation.log("hasClickableIcon: icon not visible");
             return false;
         }
-        final Rect iconBounds = icon.getVisibleBounds();
+        final Rect iconBounds = mLauncher.getVisibleBounds(icon);
         LauncherInstrumentation.log("hasClickableIcon: icon bounds: " + iconBounds);
         if (iconBounds.height() < mIconHeight / 2) {
             LauncherInstrumentation.log("hasClickableIcon: icon has insufficient height");
@@ -86,7 +86,7 @@
 
     private boolean iconCenterInSearchBox(UiObject2 allAppsContainer, UiObject2 icon) {
         final Point iconCenter = icon.getVisibleCenter();
-        return getSearchBox(allAppsContainer).getVisibleBounds().contains(
+        return mLauncher.getVisibleBounds(getSearchBox(allAppsContainer)).contains(
                 iconCenter.x, iconCenter.y);
     }
 
@@ -125,11 +125,11 @@
                                 mLauncher.getObjectsInContainer(allAppsContainer, "icon")
                                         .stream()
                                         .filter(icon ->
-                                                icon.getVisibleBounds().bottom
+                                                        mLauncher.getVisibleBounds(icon).bottom
                                                         <= displayBottom)
                                         .collect(Collectors.toList()),
-                                searchBox.getVisibleBounds().bottom
-                                        - allAppsContainer.getVisibleBounds().top);
+                                mLauncher.getVisibleBounds(searchBox).bottom
+                                        - mLauncher.getVisibleBounds(allAppsContainer).top);
                         final int newScroll = getAllAppsScroll();
                         mLauncher.assertTrue(
                                 "Scrolled in a wrong direction in AllApps: from " + scroll + " to "
@@ -166,7 +166,8 @@
             final UiObject2 searchBox = getSearchBox(allAppsContainer);
 
             int attempts = 0;
-            final Rect margins = new Rect(0, searchBox.getVisibleBounds().bottom + 1, 0, 5);
+            final Rect margins =
+                    new Rect(0, mLauncher.getVisibleBounds(searchBox).bottom + 1, 0, 5);
 
             for (int scroll = getAllAppsScroll();
                     scroll != 0;
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index a769acf..69afcc4 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -117,8 +117,8 @@
             // part) one in the center, and parts of its right and left siblings. Find the
             // main task view by its width.
             final UiObject2 widestTask = Collections.max(taskViews,
-                    (t1, t2) -> Integer.compare(t1.getVisibleBounds().width(),
-                            t2.getVisibleBounds().width()));
+                    (t1, t2) -> Integer.compare(mLauncher.getVisibleBounds(t1).width(),
+                            mLauncher.getVisibleBounds(t2).width()));
 
             return new OverviewTask(mLauncher, widestTask, this);
         }
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 2177032..2922acf 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -57,7 +57,7 @@
 
     private Background launch(BySelector selector) {
         LauncherInstrumentation.log("Launchable.launch before click " +
-                mObject.getVisibleCenter() + " in " + mObject.getVisibleBounds());
+                mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
 
         mLauncher.executeAndWaitForEvent(
                 () -> mLauncher.clickLauncherObject(mObject),
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 1861ad1..710ce9e 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -977,7 +977,7 @@
 
     int getBottomGestureMarginInContainer(UiObject2 container) {
         final int bottomGestureStartOnScreen = getRealDisplaySize().y - getBottomGestureSize();
-        return container.getVisibleBounds().bottom - bottomGestureStartOnScreen;
+        return getVisibleBounds(container).bottom - bottomGestureStartOnScreen;
     }
 
     void clickLauncherObject(UiObject2 object) {
@@ -995,10 +995,10 @@
             Collection<UiObject2> items,
             int topPaddingInContainer) {
         final UiObject2 lowestItem = Collections.max(items, (i1, i2) ->
-                Integer.compare(i1.getVisibleBounds().top, i2.getVisibleBounds().top));
+                Integer.compare(getVisibleBounds(i1).top, getVisibleBounds(i2).top));
 
-        final int itemRowCurrentTopOnScreen = lowestItem.getVisibleBounds().top;
-        final Rect containerRect = container.getVisibleBounds();
+        final int itemRowCurrentTopOnScreen = getVisibleBounds(lowestItem).top;
+        final Rect containerRect = getVisibleBounds(container);
         final int itemRowNewTopOnScreen = containerRect.top + topPaddingInContainer;
         final int distance = itemRowCurrentTopOnScreen - itemRowNewTopOnScreen + getTouchSlop();
 
@@ -1017,7 +1017,7 @@
 
     void scroll(
             UiObject2 container, Direction direction, Rect margins, int steps, boolean slowDown) {
-        final Rect rect = container.getVisibleBounds();
+        final Rect rect = getVisibleBounds(container);
         if (margins != null) {
             rect.left += margins.left;
             rect.top += margins.top;
@@ -1263,6 +1263,10 @@
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
+    public void disableSensorRotation() {
+        getTestInfo(TestProtocol.REQUEST_MOCK_SENSOR_ROTATION);
+    }
+
     public void disableDebugTracing() {
         getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
     }
@@ -1330,4 +1334,13 @@
     void expectEvent(String sequence, Pattern expected) {
         if (sCheckingEvents) sEventChecker.expectPattern(sequence, expected);
     }
+
+    Rect getVisibleBounds(UiObject2 object) {
+        try {
+            return object.getVisibleBounds();
+        } catch (Throwable t) {
+            fail(t.toString());
+            return null;
+        }
+    }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
index b8e6c0e..63a97f4 100644
--- a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
@@ -41,7 +41,7 @@
     public void launch(@NonNull String expectedPackageName) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             LauncherInstrumentation.log("OptionsPopupMenuItem before click "
-                    + mObject.getVisibleCenter() + " in " + mObject.getVisibleBounds());
+                    + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
             mLauncher.clickLauncherObject(mObject);
             if (!Build.MODEL.contains("Cuttlefish") ||
                     Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q &&
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 5c51782..fae5f19 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -56,7 +56,7 @@
                      "want to dismiss a task")) {
             verifyActiveContainer();
             // Dismiss the task via flinging it up.
-            final Rect taskBounds = mTask.getVisibleBounds();
+            final Rect taskBounds = mLauncher.getVisibleBounds(mTask);
             final int centerX = taskBounds.centerX();
             final int centerY = taskBounds.centerY();
             mLauncher.linearGesture(centerX, centerY, centerX, 0, 10, false,
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index a14d2f0..5be57c6 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -73,7 +73,7 @@
             mLauncher.scroll(
                     widgetsContainer,
                     Direction.UP,
-                    new Rect(0, 0, widgetsContainer.getVisibleBounds().width(), 0),
+                    new Rect(0, 0, mLauncher.getVisibleBounds(widgetsContainer).width(), 0),
                     FLING_STEPS, false);
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) {
                 verifyActiveContainer();
@@ -111,19 +111,19 @@
 
                     int maxWidth = 0;
                     for (UiObject2 sibling : widget.getParent().getChildren()) {
-                        maxWidth = Math.max(sibling.getVisibleBounds().width(), maxWidth);
+                        maxWidth = Math.max(mLauncher.getVisibleBounds(sibling).width(), maxWidth);
                     }
 
-                    int visibleDelta = maxWidth - widget.getVisibleBounds().width();
+                    int visibleDelta = maxWidth - mLauncher.getVisibleBounds(widget).width();
                     if (visibleDelta > 0) {
-                        Rect parentBounds = cell.getVisibleBounds();
+                        Rect parentBounds = mLauncher.getVisibleBounds(cell);
                         mLauncher.linearGesture(parentBounds.centerX() + visibleDelta
                                         + mLauncher.getTouchSlop(),
                                 parentBounds.centerY(), parentBounds.centerX(),
                                 parentBounds.centerY(), 10, true, GestureScope.INSIDE);
                     }
 
-                    if (widget.getVisibleBounds().bottom
+                    if (mLauncher.getVisibleBounds(widget).bottom
                             <= displaySize.y - mLauncher.getBottomGestureSize()) {
                         return new Widget(mLauncher, widget);
                     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 86aafc2..b3b5e32 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -177,7 +177,7 @@
                             mLauncher,
                             getHotseatAppIcon("Chrome"),
                             new Point(mLauncher.getDevice().getDisplayWidth(),
-                                    workspace.getVisibleBounds().centerY()),
+                                    mLauncher.getVisibleBounds(workspace).centerY()),
                             "deep_shortcuts_container",
                             false,
                             () -> mLauncher.expectEvent(