Merge "Dump nav mode with TIS" into ub-launcher3-rvc-dev
diff --git a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
index eac0bfa..fe57e9b 100644
--- a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
+++ b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
@@ -13,12 +13,20 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.quickstep.views.LauncherRecentsView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:accessibilityPaneTitle="@string/accessibility_recent_apps"
-    android:clipChildren="false"
-    android:clipToPadding="false"
-    android:theme="@style/HomeScreenElementTheme"
-    android:visibility="invisible" />
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <com.android.quickstep.views.LauncherRecentsView
+        android:id="@+id/overview_panel"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:accessibilityPaneTitle="@string/accessibility_recent_apps"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:theme="@style/HomeScreenElementTheme"
+        android:visibility="invisible" />
+
+    <include
+        android:id="@+id/overview_actions_view"
+        layout="@layout/overview_actions_container" />
+
+</merge>
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 8af26c6..4ef589a 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
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.DashPathEffect;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.util.AttributeSet;
@@ -205,10 +204,8 @@
             mOffsetX = icon.getOutlineOffsetX();
             mOffsetY = icon.getOutlineOffsetY();
             mIconRadius = icon.mNormalizedIconRadius;
-            mOutlinePaint.setStyle(Paint.Style.STROKE);
-            mOutlinePaint.setStrokeWidth(5);
-            mOutlinePaint.setPathEffect(new DashPathEffect(new float[]{15, 15}, 0));
-            mOutlinePaint.setColor(Color.argb(100, 245, 245, 245));
+            mOutlinePaint.setStyle(Paint.Style.FILL);
+            mOutlinePaint.setColor(Color.argb(24, 245, 245, 245));
         }
 
         /**
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 3d6e519..2f55fda 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,19 +15,14 @@
  */
 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;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 
 import android.annotation.TargetApi;
 import android.os.Build;
 import android.util.FloatProperty;
-import android.view.View;
-import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
 
@@ -37,6 +32,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.util.MultiValueAlpha;
 import com.android.quickstep.views.ClearAllButton;
 import com.android.quickstep.views.LauncherRecentsView;
 import com.android.quickstep.views.RecentsView;
@@ -60,7 +56,7 @@
             mRecentsView.updateEmptyMessage();
             mRecentsView.resetTaskVisuals();
         }
-        setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state, LINEAR);
+        setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state);
         mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
     }
 
@@ -78,22 +74,17 @@
                     AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
         }
 
-        setAlphas(builder, toState,
-                config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
+        setAlphas(builder, toState);
         builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
                 toState.getOverviewFullscreenProgress(), LINEAR);
     }
 
-    private void setAlphas(PropertySetter propertySetter, LauncherState state,
-            Interpolator actionInterpolator) {
+    private void setAlphas(PropertySetter propertySetter, LauncherState state) {
         float buttonAlpha = (state.getVisibleElements(mLauncher) & OVERVIEW_BUTTONS) != 0 ? 1 : 0;
         propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
                 buttonAlpha, LINEAR);
-
-        View actionsView = mLauncher.getActionsView();
-        if (actionsView != null) {
-            propertySetter.setFloat(actionsView, VIEW_ALPHA, buttonAlpha, actionInterpolator);
-        }
+        propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
+                MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index bcfb11c..fc28da3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -144,7 +144,6 @@
 
     @Override
     public void onStateTransitionEnd(Launcher launcher) {
-        launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
         DiscoveryBounce.showForOverviewIfNeeded(launcher);
         RecentsView recentsView = launcher.getOverviewPanel();
         AccessibilityManagerCompat.sendCustomAccessibilityEvent(
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 fadde37..3513ad5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -31,7 +31,6 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
-import android.util.Log;
 import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.View;
@@ -46,7 +45,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.views.FloatingIconView;
@@ -133,8 +131,7 @@
         mDeviceState = deviceState;
         mGestureState = gestureState;
         mActivityInterface = gestureState.getActivityInterface();
-        mActivityInitListener =
-                mActivityInterface.createActivityInitListener(this::onActivityInit);
+        mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
         mInputConsumer = inputConsumer;
         mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
         mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
@@ -325,13 +322,14 @@
             mAppWindowAnimationHelper.updateHomeBounds(getStackBounds(dp));
         }
         int displayRotation = 0;
-        if (mOrientedState != null) {
+        if (mOrientedState != null && !mOrientedState.areMultipleLayoutOrientationsDisabled()) {
             // TODO(b/150300347): The first recents animation after launcher is started with the
             //  foreground app not in landscape will look funky until that bug is fixed
             displayRotation = mOrientedState.getDisplayRotation();
 
             RectF tempRectF = new RectF(TEMP_RECT);
-            mOrientedState.mapRectFromNormalOrientation(tempRectF, dp.widthPx, dp.heightPx);
+            mOrientedState.mapRectFromRotation(displayRotation,
+                    tempRectF, dp.widthPx, dp.heightPx);
             tempRectF.roundOut(TEMP_RECT);
         }
         mAppWindowAnimationHelper.updateTargetRect(TEMP_RECT);
@@ -395,11 +393,15 @@
 
     public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
 
-    public void initWhenReady() {
+    /**
+     * Registers a callback to run when the activity is ready.
+     * @param intent The intent that will be used to start the activity if it doesn't exist already.
+     */
+    public void initWhenReady(Intent intent) {
         // Preload the plan
         RecentsModel.INSTANCE.get(mContext).getTasks(null);
 
-        mActivityInitListener.register();
+        mActivityInitListener.register(intent);
     }
 
     /**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index 46b026e..b4492d8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -188,10 +188,10 @@
     }
 
     @Override
-    public void initWhenReady() {
+    public void initWhenReady(Intent intent) {
         if (mInQuickSwitchMode) {
             // Only init if we are in quickswitch mode
-            super.initWhenReady();
+            super.initWhenReady(intent);
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
index 33fe5a9..fd17551 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
@@ -42,9 +42,10 @@
 public class ImageActionsApi {
 
     private static final String TAG = BuildConfig.APPLICATION_ID + "ImageActionsApi";
-    private final Context mContext;
-    private final Supplier<Bitmap> mBitmapSupplier;
-    private final SystemUiProxy mSystemUiProxy;
+
+    protected final Context mContext;
+    protected final Supplier<Bitmap> mBitmapSupplier;
+    protected final SystemUiProxy mSystemUiProxy;
 
     public ImageActionsApi(Context context, Supplier<Bitmap> bitmapSupplier) {
         mContext = context;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index 42d944f..52a2558 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -71,6 +71,7 @@
         mRecentsRootView = findViewById(R.id.drag_layer);
         mFallbackRecentsView = findViewById(R.id.overview_panel);
         mRecentsRootView.recreateControllers();
+        mFallbackRecentsView.init(findViewById(R.id.overview_actions_view));
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index fbf29af..147f933 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -16,7 +16,6 @@
 
 package com.android.quickstep;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 
 import android.content.Context;
@@ -84,47 +83,47 @@
     /**
      * Overlay on each task handling Overview Action Buttons.
      */
-    public static class TaskOverlay {
+    public static class TaskOverlay<T extends OverviewActionsView> {
 
         private final Context mApplicationContext;
-        private OverviewActionsView mActionsView;
-        private final TaskThumbnailView mThumbnailView;
+        protected final TaskThumbnailView mThumbnailView;
 
+        private T mActionsView;
 
         protected TaskOverlay(TaskThumbnailView taskThumbnailView) {
             mApplicationContext = taskThumbnailView.getContext().getApplicationContext();
             mThumbnailView = taskThumbnailView;
         }
 
+        protected T getActionsView() {
+            if (mActionsView == null) {
+                mActionsView = BaseActivity.fromContext(mThumbnailView.getContext()).findViewById(
+                        R.id.overview_actions_view);
+            }
+            return mActionsView;
+        }
+
         /**
          * Called when the current task is interactive for the user
          */
         public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix) {
             ImageActionsApi imageApi = new ImageActionsApi(
                     mApplicationContext, mThumbnailView::getThumbnail);
+            getActionsView().setCallbacks(new OverlayUICallbacks() {
+                @Override
+                public void onShare() {
+                    imageApi.startShareActivity();
+                }
 
-            if (mActionsView == null && ENABLE_OVERVIEW_ACTIONS.get()
-                    && SysUINavigationMode.removeShelfFromOverview(mApplicationContext)) {
-                mActionsView = BaseActivity.fromContext(mThumbnailView.getContext()).findViewById(
-                        R.id.overview_actions_view);
-            }
-            if (mActionsView != null) {
-                mActionsView.setListener(new OverviewActionsView.Listener() {
-                    @Override
-                    public void onShare() {
-                        imageApi.startShareActivity();
-                    }
-
-                    @Override
-                    public void onScreenshot() {
-                        imageApi.saveScreenshot(mThumbnailView.getThumbnail(),
-                                getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key.id);
-                    }
-                });
-            }
-
+                @Override
+                public void onScreenshot() {
+                    imageApi.saveScreenshot(mThumbnailView.getThumbnail(),
+                            getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key.id);
+                }
+            });
         }
 
+
         /**
          * Called when the overlay is no longer used.
          */
@@ -161,4 +160,16 @@
             return Insets.of(0, 0, 0, 0);
         }
     }
+
+    /**
+     * Callbacks the Ui can generate. This is the only way for a Ui to call methods on the
+     * controller.
+     */
+    public interface OverlayUICallbacks {
+        /** User has indicated they want to share the current task. */
+        void onShare();
+
+        /** User has indicated they want to screenshot the current task. */
+        void onScreenshot();
+    }
 }
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 f6fccda..9b5a935 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -90,7 +90,6 @@
 import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.InputMonitorCompat;
-import com.android.systemui.shared.system.RecentsAnimationListener;
 import com.android.systemui.shared.tracing.ProtoTraceable;
 
 import java.io.FileDescriptor;
@@ -742,12 +741,14 @@
 
         final BaseActivityInterface<BaseDraggingActivity> activityInterface =
                 mOverviewComponentObserver.getActivityInterface();
+        final Intent overviewIntent = new Intent(
+                mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
         if (activityInterface.getCreatedActivity() == null) {
             // Make sure that UI states will be initialized.
             activityInterface.createActivityInitListener((wasVisible) -> {
                 AppLaunchTracker.INSTANCE.get(TouchInteractionService.this);
                 return false;
-            }).register();
+            }).register(overviewIntent);
         } else if (fromInit) {
             // The activity has been created before the initialization of overview service. It is
             // usually happens when booting or launcher is the top activity, so we should already
@@ -755,8 +756,7 @@
             return;
         }
 
-        mTaskAnimationManager.preloadRecentsAnimation(
-                mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
+        mTaskAnimationManager.preloadRecentsAnimation(overviewIntent);
     }
 
     @Override
@@ -836,7 +836,7 @@
 
     private BaseSwipeUpHandler createLauncherSwipeHandler(GestureState gestureState,
             long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
-        return  new LauncherSwipeHandler(this, mDeviceState, mTaskAnimationManager,
+        return new LauncherSwipeHandler(this, mDeviceState, mTaskAnimationManager,
                 gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
     }
 
@@ -862,11 +862,6 @@
         }
     }
 
-    public static void startRecentsActivityAsync(Intent intent, RecentsAnimationListener listener) {
-        UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
-                .startRecentsActivity(intent, null, listener, null, null));
-    }
-
     @Override
     public void onPluginConnected(OverscrollPlugin overscrollPlugin, Context context) {
         mOverscrollPlugin = overscrollPlugin;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
index dc0c194..6d1bf11 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -30,6 +30,7 @@
 import com.android.launcher3.Utilities;
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
@@ -67,6 +68,11 @@
 
     public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    public void init(OverviewActionsView actionsView) {
+        super.init(actionsView);
         setOverviewStateEnabled(true);
         setOverlayEnabled(true);
     }
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 fe9ef2b..1f6c506 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
@@ -333,7 +333,8 @@
                 mTaskAnimationManager.isRecentsAnimationRunning(), isLikelyToStartNewTask);
         mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
         mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler::onMotionPauseChanged);
-        mInteractionHandler.initWhenReady();
+        Intent intent = new Intent(mInteractionHandler.getLaunchIntent());
+        mInteractionHandler.initWhenReady(intent);
 
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
             mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState);
@@ -341,7 +342,6 @@
             mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler);
             notifyGestureStarted();
         } else {
-            Intent intent = mInteractionHandler.getLaunchIntent();
             intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId());
             mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(mGestureState, intent,
                     mInteractionHandler);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
index 0a21413..c49b8f2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
@@ -36,17 +36,14 @@
 import com.android.launcher3.R;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
-import com.android.quickstep.views.LauncherRecentsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.plugins.OverscrollPlugin;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 /**
  * Input consumer for handling events to pass to an {@code OverscrollPlugin}.
- *
- * @param <T> Draggable activity subclass used by RecentsView
  */
-public class OverscrollInputConsumer<T extends BaseDraggingActivity> extends DelegateInputConsumer {
+public class OverscrollInputConsumer extends DelegateInputConsumer {
 
     private static final String TAG = "OverscrollInputConsumer";
 
@@ -61,12 +58,12 @@
 
     private final float mSquaredSlop;
 
-    private final Context mContext;
     private final GestureState mGestureState;
     @Nullable
     private final OverscrollPlugin mPlugin;
     private final GestureDetector mGestureDetector;
 
+    @Nullable
     private RecentsView mRecentsView;
 
     public OverscrollInputConsumer(Context context, GestureState gestureState,
@@ -77,7 +74,6 @@
                 .getInteger(R.integer.assistant_gesture_corner_deg_threshold);
         mFlingThresholdPx = context.getResources()
             .getDimension(R.dimen.gestures_overscroll_fling_threshold);
-        mContext = context;
         mGestureState = gestureState;
         mPlugin = plugin;
 
@@ -85,9 +81,6 @@
 
         mSquaredSlop = slop * slop;
         mGestureDetector = new GestureDetector(context, new FlingGestureListener());
-
-        gestureState.getActivityInterface().createActivityInitListener(this::onActivityInit)
-                .register();
     }
 
     @Override
@@ -95,12 +88,6 @@
         return TYPE_OVERSCROLL | mDelegate.getType();
     }
 
-    private boolean onActivityInit(Boolean alreadyOnHome) {
-        mRecentsView = mGestureState.getActivityInterface().getCreatedActivity().getOverviewPanel();
-
-        return true;
-    }
-
     @Override
     public void onMotionEvent(MotionEvent ev) {
         switch (ev.getActionMasked()) {
@@ -191,10 +178,17 @@
     }
 
     private boolean isOverscrolled() {
+        if (mRecentsView == null) {
+            BaseDraggingActivity activity = mGestureState.getActivityInterface()
+                    .getCreatedActivity();
+            if (activity != null) {
+                mRecentsView = activity.getOverviewPanel();
+            }
+        }
+
         // Make sure there isn't an app to quick switch to on our right
         int maxIndex = 0;
-        if ((mRecentsView instanceof LauncherRecentsView)
-                && ((LauncherRecentsView) mRecentsView).hasRecentsExtraCard()) {
+        if (mRecentsView != null && mRecentsView.hasRecentsExtraCard()) {
             maxIndex = 1;
         }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
index 75a5976..b739838 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
@@ -160,9 +160,9 @@
     }
 
     private float getSrcToTargetScale() {
-        if (mOrientedState == null ||
-            (mOrientedState.getDisplayRotation() == Surface.ROTATION_0
-                || mOrientedState.getDisplayRotation() == Surface.ROTATION_180)) {
+        if (mOrientedState == null
+                || mOrientedState.isHomeRotationAllowed()
+                || mOrientedState.isDisplayPhoneNatural()) {
             return mSourceRect.width() / mTargetRect.width();
         } else {
             return mSourceRect.height() / mTargetRect.height();
@@ -277,8 +277,9 @@
                 mCurrentRect.offset(params.mOffset, 0);
             } else {
                 int displayRotation = mOrientedState.getDisplayRotation();
+                int launcherRotation = mOrientedState.getLauncherRotation();
                 mOrientedState.getOrientationHandler().offsetTaskRect(mCurrentRect,
-                    params.mOffset, displayRotation);
+                    params.mOffset, displayRotation, launcherRotation);
             }
         }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 98eb29a..78d75c5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -47,7 +47,6 @@
 import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.views.ScrimView;
@@ -88,10 +87,6 @@
         }
     };
 
-    private RotationHelper.ForcedRotationChangedListener mForcedRotationChangedListener =
-            isForcedRotation -> LauncherRecentsView.this
-                    .disableMultipleLayoutRotations(!isForcedRotation);
-
     public LauncherRecentsView(Context context) {
         this(context, null);
     }
@@ -102,11 +97,16 @@
 
     public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        setContentAlpha(0);
         mActivity.getStateManager().addStateListener(this);
     }
 
     @Override
+    public void init(OverviewActionsView actionsView) {
+        super.init(actionsView);
+        setContentAlpha(0);
+    }
+
+    @Override
     public void startHome() {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             switchToScreenshot(null,
@@ -344,7 +344,6 @@
         super.onAttachedToWindow();
         PluginManagerWrapper.INSTANCE.get(getContext()).addPluginListener(
                 mRecentsExtraCardPluginListener, RecentsExtraCard.class);
-        mActivity.getRotationHelper().addForcedRotationCallback(mForcedRotationChangedListener);
     }
 
     @Override
@@ -352,7 +351,6 @@
         super.onDetachedFromWindow();
         PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(
                 mRecentsExtraCardPluginListener);
-        mActivity.getRotationHelper().removeForcedRotationCallback(mForcedRotationChangedListener);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
index 6a37e2b..93e68c0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
@@ -16,34 +16,59 @@
 
 package com.android.quickstep.views;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+
 import android.content.Context;
 import android.util.AttributeSet;
-import android.view.LayoutInflater;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.widget.FrameLayout;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.R;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * View for showing action buttons in Overview
  */
-public class OverviewActionsView extends FrameLayout {
+public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayout
+        implements OnClickListener {
 
-    private final View mScreenshotButton;
-    private final View mShareButton;
+    @IntDef(flag = true, value = {
+            HIDDEN_UNSUPPORTED_NAVIGATION,
+            HIDDEN_DISABLED_FEATURE,
+            HIDDEN_NON_ZERO_ROTATION,
+            HIDDEN_NO_TASKS,
+            HIDDEN_GESTURE_RUNNING,
+            HIDDEN_NO_RECENTS})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ActionsHiddenFlags { }
 
-    /**
-     * Listener for taps on the various actions.
-     */
-    public interface Listener {
-        /** User has initiated the share actions. */
-        void onShare();
+    public static final int HIDDEN_UNSUPPORTED_NAVIGATION = 1 << 0;
+    public static final int HIDDEN_DISABLED_FEATURE = 1 << 1;
+    public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 2;
+    public static final int HIDDEN_NO_TASKS = 1 << 3;
+    public static final int HIDDEN_GESTURE_RUNNING = 1 << 4;
+    public static final int HIDDEN_NO_RECENTS = 1 << 5;
 
-        /** User has initiated the screenshot action. */
-        void onScreenshot();
-    }
+    private static final int INDEX_CONTENT_ALPHA = 0;
+    private static final int INDEX_VISIBILITY_ALPHA = 1;
+    private static final int INDEX_HIDDEN_FLAGS_ALPHA = 2;
+
+    private final MultiValueAlpha mMultiValueAlpha;
+
+    @ActionsHiddenFlags
+    private int mHiddenFlags;
+
+    protected T mCallbacks;
 
     public OverviewActionsView(Context context) {
         this(context, null);
@@ -54,26 +79,62 @@
     }
 
     public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
+        super(context, attrs, defStyleAttr, 0);
+        mMultiValueAlpha = new MultiValueAlpha(this, 3);
     }
 
-    public OverviewActionsView(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        LayoutInflater.from(context).inflate(R.layout.overview_actions, this, true);
-        mShareButton = findViewById(R.id.action_share);
-        mScreenshotButton = findViewById(R.id.action_screenshot);
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        findViewById(R.id.action_share).setOnClickListener(this);
+        findViewById(R.id.action_screenshot).setOnClickListener(this);
     }
 
     /**
      * Set listener for callbacks on action button taps.
      *
-     * @param listener for callbacks, or {@code null} to clear the listener.
+     * @param callbacks for callbacks, or {@code null} to clear the listener.
      */
-    public void setListener(@Nullable OverviewActionsView.Listener listener) {
-        mShareButton.setOnClickListener(
-                listener == null ? null : view -> listener.onShare());
-        mScreenshotButton.setOnClickListener(
-                listener == null ? null : view -> listener.onScreenshot());
+    public void setCallbacks(T callbacks) {
+        mCallbacks = callbacks;
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (mCallbacks == null) {
+            return;
+        }
+        int id = view.getId();
+        if (id == R.id.action_share) {
+            mCallbacks.onShare();
+        } else if (id == R.id.action_screenshot) {
+            mCallbacks.onScreenshot();
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        updateHiddenFlags(HIDDEN_DISABLED_FEATURE, !ENABLE_OVERVIEW_ACTIONS.get());
+        updateHiddenFlags(HIDDEN_UNSUPPORTED_NAVIGATION, !removeShelfFromOverview(getContext()));
+    }
+
+    public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
+        if (enable) {
+            mHiddenFlags |= visibilityFlags;
+        } else {
+            mHiddenFlags &= ~visibilityFlags;
+        }
+        boolean isHidden = mHiddenFlags != 0;
+        mMultiValueAlpha.getProperty(INDEX_HIDDEN_FLAGS_ALPHA).setValue(isHidden ? 0 : 1);
+        setVisibility(isHidden ? INVISIBLE : VISIBLE);
+    }
+
+    public AlphaProperty getContentAlpha() {
+        return mMultiValueAlpha.getProperty(INDEX_CONTENT_ALPHA);
+    }
+
+    public AlphaProperty getVisibilityAlpha() {
+        return mMultiValueAlpha.getProperty(INDEX_VISIBILITY_ALPHA);
     }
 }
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 a18f7ba..24eae18 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
@@ -28,7 +28,6 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.TASK_DISMISS_SWIPE_UP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.TASK_LAUNCH_SWIPE_DOWN;
@@ -39,6 +38,10 @@
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_GESTURE_RUNNING;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
 
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
@@ -50,6 +53,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -66,12 +70,12 @@
 import android.util.FloatProperty;
 import android.util.Property;
 import android.util.SparseBooleanArray;
-import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.OrientationEventListener;
+import android.view.Surface;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
@@ -85,7 +89,6 @@
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
-import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.PagedView;
@@ -115,7 +118,6 @@
 import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
-import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
@@ -128,6 +130,7 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.ConfigurationCompat;
 import com.android.systemui.shared.system.LauncherEventUtil;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
@@ -172,7 +175,7 @@
                 }
             };
 
-    protected final RecentsOrientedState mOrientationState = new RecentsOrientedState();
+    protected RecentsOrientedState mOrientationState;
 
     private OrientationEventListener mOrientationListener;
     private int mPreviousRotation;
@@ -328,8 +331,7 @@
 
     // Keeps track of the index where the first TaskView should be
     private int mTaskViewStartIndex = 0;
-    private View mActionsView;
-    private boolean mGestureRunning = false;
+    private OverviewActionsView mActionsView;
 
     private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
             (inMultiWindowMode) -> {
@@ -343,6 +345,7 @@
         super(context, attrs, defStyleAttr);
         setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
         setEnableFreeScroll(true);
+        mOrientationState = new RecentsOrientedState(context);
 
         mFastFlingVelocity = getResources()
                 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
@@ -389,11 +392,6 @@
                 int rotation = RecentsOrientedState.getRotationForUserDegreesRotated(i);
                 if (mPreviousRotation != rotation) {
                     animateRecentsRotationInPlace(rotation);
-                    if (rotation == 0) {
-                        showActionsView();
-                    } else {
-                        hideActionsView();
-                    }
                     mPreviousRotation = rotation;
                 }
             }
@@ -468,6 +466,10 @@
         reset();
     }
 
+    public void init(OverviewActionsView actionsView) {
+        mActionsView = actionsView;
+    }
+
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
@@ -481,7 +483,7 @@
         mIPinnedStackAnimationListener.setActivity(mActivity);
         SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
                 mIPinnedStackAnimationListener);
-        setActionsView();
+        mOrientationState.init();
     }
 
     @Override
@@ -496,6 +498,7 @@
         mIdp.removeOnChangeListener(this);
         SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
         mIPinnedStackAnimationListener.setActivity(null);
+        mOrientationState.destroy();
     }
 
     @Override
@@ -507,6 +510,7 @@
             TaskView taskView = (TaskView) child;
             mHasVisibleTaskData.delete(taskView.getTask().key.id);
             mTaskViewPool.recycle(taskView);
+            mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
         }
         updateTaskStartIndex(child);
     }
@@ -519,6 +523,7 @@
         // child direction back to match system settings.
         child.setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_LTR : View.LAYOUT_DIRECTION_RTL);
         updateTaskStartIndex(child);
+        mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, false);
     }
 
     private void updateTaskStartIndex(View affectingView) {
@@ -549,9 +554,7 @@
     }
 
     public void setOverviewStateEnabled(boolean enabled) {
-        if (supportsVerticalLandscape()
-                && !TestProtocol.sDisableSensorRotation // Ignore hardware dependency for tests
-                && mOrientationListener.canDetectOrientation()) {
+        if (canEnableOverviewRotationAnimation()) {
             if (enabled) {
                 mOrientationListener.enable();
             } else {
@@ -567,6 +570,13 @@
         }
     }
 
+    private boolean canEnableOverviewRotationAnimation() {
+        return supportsVerticalLandscape() // not 3P launcher
+                && !TestProtocol.sDisableSensorRotation // Ignore hardware dependency for tests..
+                && mOrientationListener.canDetectOrientation() // ..but does the hardware even work?
+                && !mOrientationState.canLauncherAutoRotate(); // launcher is going to rotate itself
+    }
+
     public void onDigitalWellbeingToastShown() {
         if (!mDwbToastShown) {
             mDwbToastShown = true;
@@ -585,6 +595,15 @@
     }
 
     @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        int windowConfigurationRotation = ConfigurationCompat
+                .getWindowConfigurationRotation(getResources().getConfiguration());
+        setLayoutInternal(mOrientationState.getTouchRotation(),
+                mOrientationState.getDisplayRotation(), windowConfigurationRotation);
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent ev) {
         super.onTouchEvent(ev);
         final int x = (int) ev.getX();
@@ -668,7 +687,6 @@
         if (getTaskViewCount() != requiredTaskCount) {
             if (indexOfChild(mClearAllButton) != -1) {
                 removeView(mClearAllButton);
-                hideActionsView();
             }
             for (int i = getTaskViewCount(); i < requiredTaskCount; i++) {
                 addView(mTaskViewPool.getView());
@@ -678,7 +696,6 @@
             }
             if (requiredTaskCount > 0) {
                 addView(mClearAllButton);
-                showActionsView();
             }
         }
 
@@ -718,7 +735,6 @@
         if (indexOfChild(mClearAllButton) != -1) {
             removeView(mClearAllButton);
         }
-        hideActionsView();
     }
 
     public int getTaskViewCount() {
@@ -778,7 +794,7 @@
         for (int i = 0; i < taskCount; i++) {
             getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
         }
-        if (mActionsView != null) {
+        if (mActionsView != null && mOrientationState.getLauncherRotation() == Surface.ROTATION_0) {
             mActionsView.setVisibility(fullscreenProgress == 0 ? VISIBLE : INVISIBLE);
         }
     }
@@ -980,7 +996,7 @@
         setEnableDrawingLiveTile(false);
         setRunningTaskHidden(true);
         setRunningTaskIconScaledDown(true);
-        mGestureRunning = true;
+        mActionsView.updateHiddenFlags(HIDDEN_GESTURE_RUNNING, true);
     }
 
     /**
@@ -1046,7 +1062,7 @@
         }
         setRunningTaskHidden(false);
         animateUpRunningTaskIconScale();
-        mGestureRunning = false;
+        mActionsView.updateHiddenFlags(HIDDEN_GESTURE_RUNNING, false);
     }
 
     /**
@@ -1063,7 +1079,6 @@
             addView(taskView, mTaskViewStartIndex);
             if (wasEmpty) {
                 addView(mClearAllButton);
-                showActionsView();
             }
             // The temporary running task is only used for the duration between the start of the
             // gesture and the task list is loaded and applied
@@ -1387,7 +1402,6 @@
 
                     if (getTaskViewCount() == 0) {
                         removeViewInLayout(mClearAllButton);
-                        hideActionsView();
                         startHome();
                     } else {
                         snapToPageImmediately(pageToSnapTo);
@@ -1530,14 +1544,12 @@
         int alphaInt = Math.round(alpha * 255);
         mEmptyMessagePaint.setAlpha(alphaInt);
         mEmptyIcon.setAlpha(alphaInt);
+        mActionsView.getContentAlpha().setValue(mContentAlpha);
+
         if (alpha > 0) {
             setVisibility(VISIBLE);
-            if (!mGestureRunning) {
-                showActionsView();
-            }
         } else if (!mFreezeViewVisibility) {
             setVisibility(GONE);
-            hideActionsView();
         }
     }
 
@@ -1548,24 +1560,33 @@
     public void setFreezeViewVisibility(boolean freezeViewVisibility) {
         if (mFreezeViewVisibility != freezeViewVisibility) {
             mFreezeViewVisibility = freezeViewVisibility;
-
             if (!mFreezeViewVisibility) {
                 setVisibility(mContentAlpha > 0 ? VISIBLE : GONE);
-                if (mContentAlpha > 0) {
-                    showActionsView();
-                } else {
-                    hideActionsView();
-                }
             }
         }
     }
 
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+        if (mActionsView != null) {
+            mActionsView.updateHiddenFlags(HIDDEN_NO_RECENTS, visibility != VISIBLE);
+        }
+    }
+
     public void setLayoutRotation(int touchRotation, int displayRotation) {
-        if (mOrientationState.update(touchRotation, displayRotation)) {
+        int launcherRotation = mOrientationState.getLauncherRotation();
+        setLayoutInternal(touchRotation, displayRotation, launcherRotation);
+    }
+
+    private void setLayoutInternal(int touchRotation, int displayRotation, int launcherRotation) {
+        if (mOrientationState.update(touchRotation, displayRotation, launcherRotation)) {
             mOrientationHandler = mOrientationState.getOrientationHandler();
             mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
             setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
             mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated());
+            mActivity.getDragLayer().recreateControllers();
+            mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, touchRotation != 0);
             requestLayout();
         }
     }
@@ -2022,7 +2043,8 @@
     public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
         float degreesRotated;
         if (navbarRotation == 0) {
-            degreesRotated = mOrientationState.getTouchRotationDegrees();
+            degreesRotated = mOrientationState.areMultipleLayoutOrientationsDisabled() ? 0 :
+                    mOrientationHandler.getDegreesRotated();
         } else {
             degreesRotated = -navbarRotation;
         }
@@ -2035,7 +2057,8 @@
         // PagedOrientationHandler
         return e -> {
             if (navbarRotation != 0
-                    && !mOrientationState.areMultipleLayoutOrientationsDisabled()) {
+                    && !mOrientationState.areMultipleLayoutOrientationsDisabled()
+                    && !mOrientationState.getOrientationHandler().isLayoutNaturalToLauncher()) {
                 mOrientationState.flipVertical(e);
                 super.onTouchEvent(e);
                 mOrientationState.flipVertical(e);
@@ -2122,36 +2145,4 @@
             mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
         }
     }
-
-    private void showActionsView() {
-        if (mActionsView != null && getTaskViewCount() > 0) {
-            mActionsView.setVisibility(VISIBLE);
-        }
-    }
-
-    private void hideActionsView() {
-        if (mActionsView != null) {
-            mActionsView.setVisibility(GONE);
-        }
-    }
-
-    private void setActionsView() {
-        if (mActionsView == null && ENABLE_OVERVIEW_ACTIONS.get()
-                && SysUINavigationMode.removeShelfFromOverview(mActivity)) {
-            mActionsView = ((ViewGroup) getParent()).findViewById(R.id.overview_actions_view);
-            if (mActionsView != null) {
-                InsettableFrameLayout.LayoutParams layoutParams =
-                        new InsettableFrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
-                                getResources().getDimensionPixelSize(
-                                        R.dimen.overview_actions_height));
-                layoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
-                int margin = getResources().getDimensionPixelSize(
-                        R.dimen.overview_actions_horizontal_margin);
-                layoutParams.setMarginStart(margin);
-                layoutParams.setMarginEnd(margin);
-                mActionsView.setLayoutParams(layoutParams);
-                showActionsView();
-            }
-        }
-    }
 }
diff --git a/quickstep/res/layout/overview_actions.xml b/quickstep/res/layout/overview_actions.xml
deleted file mode 100644
index ad5efb6..0000000
--- a/quickstep/res/layout/overview_actions.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-    <LinearLayout
-        android:id="@+id/action_buttons"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:orientation="horizontal">
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="1dp"
-            android:layout_weight="1" >
-        </Space>
-        <Button
-            android:id="@+id/action_screenshot"
-            style="@style/OverviewActionButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:drawableTop="@drawable/ic_screenshot"
-            android:text="@string/action_screenshot" />
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="1dp"
-            android:layout_weight="1" >
-        </Space>
-
-        <Button
-            android:id="@+id/action_share"
-            style="@style/OverviewActionButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:drawableTop="@drawable/ic_share"
-            android:text="@string/action_share" />
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="1dp"
-            android:layout_weight="1" >
-        </Space>
-    </LinearLayout>
-
-</merge>
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index 328c20b..e163991 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -16,8 +16,48 @@
 -->
 <com.android.quickstep.views.OverviewActionsView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:visibility="gone">
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/overview_actions_height"
+    android:layout_gravity="center_horizontal|bottom"
+    android:layout_marginLeft="@dimen/overview_actions_horizontal_margin"
+    android:layout_marginRight="@dimen/overview_actions_horizontal_margin" >
+
+    <LinearLayout
+        android:id="@+id/action_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:orientation="horizontal">
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1" >
+        </Space>
+        <Button
+            android:id="@+id/action_screenshot"
+            style="@style/OverviewActionButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableTop="@drawable/ic_screenshot"
+            android:text="@string/action_screenshot" />
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1" >
+        </Space>
+
+        <Button
+            android:id="@+id/action_share"
+            style="@style/OverviewActionButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableTop="@drawable/ic_share"
+            android:text="@string/action_share" />
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1" >
+        </Space>
+    </LinearLayout>
 
 </com.android.quickstep.views.OverviewActionsView>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 48e25bd..b7abd61 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -28,7 +28,6 @@
 import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.os.CancellationSignal;
-import android.view.View;
 
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.LauncherStateManager.StateHandler;
@@ -52,6 +51,7 @@
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -75,7 +75,7 @@
 
     private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this);
 
-    private View mActionsView;
+    private OverviewActionsView mActionsView;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -164,19 +164,17 @@
     protected void setupViews() {
         super.setupViews();
         mActionsView = findViewById(R.id.overview_actions_view);
-
+        ((RecentsView) getOverviewPanel()).init(mActionsView);
 
         if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(this)) {
             // Overview is above all other launcher elements, including qsb, so move it to the top.
             getOverviewPanel().bringToFront();
-            if (mActionsView != null) {
-                mActionsView.bringToFront();
-            }
+            mActionsView.bringToFront();
         }
     }
 
-    public View getActionsView() {
-        return mActionsView;
+    public <T extends OverviewActionsView> T getActionsView() {
+        return (T) mActionsView;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index fbd7a8a..7fb0d43 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -23,7 +23,6 @@
 import android.os.CancellationSignal;
 import android.os.Handler;
 
-import com.android.launcher3.util.ActivityTracker;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -45,7 +44,7 @@
     }
 
     @Override
-    public boolean init(Launcher launcher, boolean alreadyOnHome) {
+    public boolean handleInit(Launcher launcher, boolean alreadyOnHome) {
         if (mRemoteAnimationProvider != null) {
             QuickstepAppTransitionManagerImpl appTransitionManager =
                     (QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
@@ -71,7 +70,7 @@
             }, cancellationSignal);
         }
         launcher.deferOverlayCallbacksUntilNextResumeOrStop();
-        return super.init(launcher, alreadyOnHome);
+        return super.handleInit(launcher, alreadyOnHome);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 495c092..2e99500 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -22,6 +22,7 @@
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
+import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
 
 import android.content.res.Resources;
@@ -138,7 +139,8 @@
      * @param info The current displayInfo
      */
     void enableMultipleRegions(boolean enableMultipleRegions, DefaultDisplay.Info info) {
-        mEnableMultipleRegions = enableMultipleRegions;
+        mEnableMultipleRegions = enableMultipleRegions &&
+                mMode != SysUINavigationMode.Mode.TWO_BUTTONS;
         if (!enableMultipleRegions) {
             mQuickStepStartingRotation = -1;
             resetSwipeRegions(info);
@@ -364,16 +366,4 @@
             return false;
         }
     }
-
-    /**
-     * @return how many factors {@param newRotation} is rotated 90 degrees clockwise.
-     * E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise...
-     * A value of 0 means no rotation has been applied
-     */
-    @SurfaceRotation
-    private static int deltaRotation(int oldRotation, int newRotation) {
-        int delta = newRotation - oldRotation;
-        if (delta < 0) delta += 4;
-        return delta;
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
index b1c72ce..2a0fe32 100644
--- a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
+++ b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
@@ -31,6 +31,8 @@
     private final BiPredicate<T, Boolean> mOnInitListener;
     private final ActivityTracker<T> mActivityTracker;
 
+    private boolean mIsRegistered = false;
+
     /**
      * @param onInitListener a callback made when the activity is initialized. The callback should
      *                       return true to continue receiving callbacks (ie. for if the activity is
@@ -43,27 +45,45 @@
     }
 
     @Override
-    public boolean init(T activity, boolean alreadyOnHome) {
+    public final boolean init(T activity, boolean alreadyOnHome) {
+        if (!mIsRegistered) {
+            return false;
+        }
+        return handleInit(activity, alreadyOnHome);
+    }
+
+    protected boolean handleInit(T activity, boolean alreadyOnHome) {
         return mOnInitListener.test(activity, alreadyOnHome);
     }
 
     /**
      * Registers the activity-created listener. If the activity is already created, then the
      * callback provided in the constructor will be called synchronously.
+     * @param intent The intent that will be used to initialize the activity, if the activity
+     *               doesn't already exist. We add the callback as an extra on this intent.
      */
-    public void register() {
-        mActivityTracker.schedule(this);
+    public void register(Intent intent) {
+        mIsRegistered = true;
+        mActivityTracker.runCallbackWhenActivityExists(this, intent);
     }
 
+    /**
+     * After calling this, we won't {@link #init} even when the activity is ready.
+     */
     public void unregister() {
-        mActivityTracker.clearReference(this);
+        mIsRegistered = false;
     }
 
+    /**
+     * Starts the given intent with the provided animation. Unlike {@link #register(Intent)}, this
+     * method will not call {@link #init} if the activity already exists, it will only call it when
+     * we get handleIntent() for the provided intent that we're starting.
+     */
     public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
             Context context, Handler handler, long duration) {
-        register();
+        mIsRegistered = true;
 
         Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
-        context.startActivity(addToIntent(new Intent((intent))), options);
+        context.startActivity(addToIntent(new Intent(intent)), options);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index f72e458..eefe8ac 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -16,20 +16,36 @@
 
 package com.android.quickstep.util;
 
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.hardware.camera2.params.OutputConfiguration.ROTATION_180;
+import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
+import static com.android.launcher3.config.FeatureFlags.FLAG_ENABLE_FIXED_ROTATION_TRANSFORM;
+import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
+import static com.android.launcher3.states.RotationHelper.FIXED_ROTATION_TRANSFORM_SETTING_NAME;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.graphics.Matrix;
 import android.graphics.RectF;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.Surface;
 
 import androidx.annotation.IntDef;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.touch.PortraitPagedViewHandler;
@@ -44,8 +60,17 @@
  * This class has initial default state assuming the device and foreground app have
  * no ({@link Surface#ROTATION_0} rotation.
  */
-public final class RecentsOrientedState {
+public final class RecentsOrientedState implements SharedPreferences.OnSharedPreferenceChangeListener {
 
+    private static final String TAG = "RecentsOrientedState";
+    private static final boolean DEBUG = false;
+
+    private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
+        @Override
+        public void onChange(boolean selfChange) {
+            updateAutoRotateSetting();
+        }
+    };
     @Retention(SOURCE)
     @IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
     public @interface SurfaceRotation {}
@@ -54,15 +79,45 @@
 
     private @SurfaceRotation int mTouchRotation = ROTATION_0;
     private @SurfaceRotation int mDisplayRotation = ROTATION_0;
+    private @SurfaceRotation int mLauncherRotation = Surface.ROTATION_0;
+
     /**
      * If {@code true} we default to {@link PortraitPagedViewHandler} and don't support any fake
      * launcher orientations.
      */
     private boolean mDisableMultipleOrientations;
+    private boolean mIsHomeRotationAllowed;
+    private boolean mIsSystemRotationAllowed;
+
+    private final ContentResolver mContentResolver;
+    private final SharedPreferences mSharedPrefs;
+    private final boolean mAllowConfigurationDefaultValue;
+
 
     private final Matrix mTmpMatrix = new Matrix();
     private final Matrix mTmpInverseMatrix = new Matrix();
 
+    public RecentsOrientedState(Context context) {
+        mContentResolver = context.getContentResolver();
+        mSharedPrefs = Utilities.getPrefs(context);
+
+        Resources res = context.getResources();
+        int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp
+                * res.getDisplayMetrics().densityDpi / DENSITY_DEVICE_STABLE;
+        mAllowConfigurationDefaultValue = originalSmallestWidth >= 600;
+
+        boolean isForcedRotation = Utilities.getFeatureFlagsPrefs(context)
+                .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true)
+                && !mAllowConfigurationDefaultValue;
+        UI_HELPER_EXECUTOR.execute(() -> {
+            if (context.checkSelfPermission(WRITE_SECURE_SETTINGS) == PERMISSION_GRANTED) {
+                Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME,
+                        isForcedRotation ? 1 : 0);
+            }
+        });
+        disableMultipleOrientations(!isForcedRotation);
+    }
+
     /**
      * Sets the appropriate {@link PagedOrientationHandler} for {@link #mOrientationHandler}
      * @param touchRotation The rotation the nav bar region that is touched is in
@@ -72,19 +127,33 @@
      *         false otherwise
      */
     public boolean update(
-            @SurfaceRotation int touchRotation, @SurfaceRotation int displayRotation) {
+            @SurfaceRotation int touchRotation, @SurfaceRotation int displayRotation,
+            int launcherRotation) {
         if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
             return false;
         }
         if (mDisableMultipleOrientations) {
             return false;
         }
-        if (mDisplayRotation == displayRotation && mTouchRotation == touchRotation) {
+        if (mDisplayRotation == displayRotation && mTouchRotation == touchRotation
+                && launcherRotation == mLauncherRotation) {
             return false;
         }
 
+        mLauncherRotation = launcherRotation;
         mDisplayRotation = displayRotation;
         mTouchRotation = touchRotation;
+
+        if ((mIsHomeRotationAllowed && mIsSystemRotationAllowed) ||
+                mLauncherRotation == mTouchRotation) {
+            // TODO(b/153476489) Need to determine when launcher is rotated
+            mOrientationHandler = PagedOrientationHandler.HOME_ROTATED;
+            if (DEBUG) {
+                Log.d(TAG, "Set Orientation Handler: " + mOrientationHandler);
+            }
+            return true;
+        }
+
         if (mTouchRotation == ROTATION_90) {
             mOrientationHandler = PagedOrientationHandler.LANDSCAPE;
         } else if (mTouchRotation == ROTATION_270) {
@@ -92,6 +161,9 @@
         } else {
             mOrientationHandler = PagedOrientationHandler.PORTRAIT;
         }
+        if (DEBUG) {
+            Log.d(TAG, "Set Orientation Handler: " + mOrientationHandler);
+        }
         return true;
     }
 
@@ -99,8 +171,12 @@
         return mDisableMultipleOrientations;
     }
 
+    public boolean canLauncherAutoRotate() {
+        return mIsHomeRotationAllowed && mIsSystemRotationAllowed;
+    }
+
     /**
-     * Setting this preference will render future calls to {@link #update(int, int)} as a no-op.
+     * Setting this preference renders future calls to {@link #update(int, int, int)} as a no-op.
      */
     public void disableMultipleOrientations(boolean disable) {
         mDisableMultipleOrientations = disable;
@@ -110,6 +186,39 @@
         }
     }
 
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+        updateHomeRotationSetting();
+    }
+
+    private void updateAutoRotateSetting() {
+        try {
+            mIsSystemRotationAllowed = Settings.System.getInt(mContentResolver,
+                    Settings.System.ACCELEROMETER_ROTATION) == 1;
+        } catch (Settings.SettingNotFoundException e) {
+            Log.e(TAG, "autorotate setting not found", e);
+        }
+    }
+
+    private void updateHomeRotationSetting() {
+        mIsHomeRotationAllowed = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+                mAllowConfigurationDefaultValue);
+    }
+
+    public void init() {
+        mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
+        mContentResolver.registerContentObserver(
+                Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
+                false, mSystemAutoRotateObserver);
+        updateAutoRotateSetting();
+        updateHomeRotationSetting();
+    }
+
+    public void destroy() {
+        mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
+        mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver);
+    }
+
     @SurfaceRotation
     public int getDisplayRotation() {
         return mDisplayRotation;
@@ -120,6 +229,14 @@
         return mTouchRotation;
     }
 
+    public boolean isHomeRotationAllowed() {
+        return mIsHomeRotationAllowed;
+    }
+
+    public int getLauncherRotation() {
+        return mLauncherRotation;
+    }
+
     public int getTouchRotationDegrees() {
         switch (mTouchRotation) {
             case ROTATION_90:
@@ -166,8 +283,12 @@
     }
 
     public void mapRectFromNormalOrientation(RectF src, int screenWidth, int screenHeight) {
+        mapRectFromRotation(mDisplayRotation, src, screenWidth, screenHeight);
+    }
+
+    public void mapRectFromRotation(int rotation, RectF src, int screenWidth, int screenHeight) {
         mTmpMatrix.reset();
-        postDisplayRotation(mDisplayRotation, screenWidth, screenHeight, mTmpMatrix);
+        postDisplayRotation(rotation, screenWidth, screenHeight, mTmpMatrix);
         mTmpMatrix.mapRect(src);
     }
 
@@ -192,6 +313,10 @@
         }
     }
 
+    public boolean isDisplayPhoneNatural() {
+        return mDisplayRotation == Surface.ROTATION_0 || mDisplayRotation == Surface.ROTATION_180;
+    }
+
     /**
      * Posts the transformation on the matrix representing the provided display rotation
      */
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index de13277..a137908 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -45,12 +45,8 @@
 
         <include
             android:id="@+id/overview_panel"
-            layout="@layout/overview_panel"
-            android:visibility="gone" />
+            layout="@layout/overview_panel" />
 
-        <include
-            android:id="@+id/overview_actions_view"
-            layout="@layout/overview_actions_container" />
 
         <!-- Keep these behind the workspace so that they are not visible when
          we go into AllApps -->
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index 2637f03..f513688 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -17,4 +17,5 @@
 <Space
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="0dp"
-    android:layout_height="0dp" />
\ No newline at end of file
+    android:layout_height="0dp"
+    android:visibility="gone" />
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 41eeb78..8eceec0 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -183,10 +183,6 @@
     public void onScrollStateChanged(int state) {
         super.onScrollStateChanged(state);
 
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onScrollStateChanged: " + state);
-        }
-
         if (state == SCROLL_STATE_IDLE) {
             AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
         }
@@ -196,10 +192,6 @@
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         if (isLayoutSuppressed()) info.setScrollable(false);
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS,
-                    "onInitializeAccessibilityNodeInfo, scrollable: " + info.isScrollable());
-        }
     }
 
     @Override
@@ -207,12 +199,8 @@
         final boolean changing = frozen != isLayoutSuppressed();
         super.setLayoutFrozen(frozen);
         if (changing) {
-            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-                Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "setLayoutFrozen " + frozen
-                        + " @ " + Log.getStackTraceString(new Throwable()));
-                ActivityContext.lookupContext(getContext()).getDragLayer()
-                        .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
-            }
+            ActivityContext.lookupContext(getContext()).getDragLayer()
+                    .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
         }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 93247ab..e8e88c4 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -29,6 +29,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -70,7 +71,7 @@
  * too aggressive.
  */
 public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
-        IconLabelDotView, DraggableView {
+        IconLabelDotView, DraggableView, Reorderable {
 
     private static final int DISPLAY_WORKSPACE = 0;
     private static final int DISPLAY_ALL_APPS = 1;
@@ -78,6 +79,8 @@
 
     private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed};
 
+    private final PointF mTranslationForReorder = new PointF(0, 0);
+    private float mScaleForReorder = 1f;
 
     private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
             = new Property<BubbleTextView, Float>(Float.TYPE, "dotScale") {
@@ -672,6 +675,30 @@
         return mIconSize;
     }
 
+    public void setReorderOffset(float x, float y) {
+        mTranslationForReorder.set(x, y);
+        super.setTranslationX(x);
+        super.setTranslationY(y);
+    }
+
+    public void getReorderOffset(PointF offset) {
+        offset.set(mTranslationForReorder);
+    }
+
+    public void setReorderScale(float scale) {
+        mScaleForReorder = scale;
+        super.setScaleX(scale);
+        super.setScaleY(scale);
+    }
+
+    public float getReorderScale() {
+        return mScaleForReorder;
+    }
+
+    public View getView() {
+        return this;
+    }
+
     @Override
     public int getViewType() {
         return DRAGGABLE_ICON;
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 99416c4..71a787f 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -33,6 +33,7 @@
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -54,7 +55,6 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.PreviewBackground;
@@ -66,7 +66,6 @@
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -99,6 +98,7 @@
     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
     @Thunk final int[] mTmpPoint = new int[2];
     @Thunk final int[] mTempLocation = new int[2];
+    final PointF mTmpPointF = new PointF();
 
     // Used to visualize / debug the Grid of the CellLayout
     private static final boolean VISUALIZE_GRID = false;
@@ -136,7 +136,7 @@
     private final Paint mDragOutlinePaint = new Paint();
 
     @Thunk final ArrayMap<LayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
-    @Thunk final ArrayMap<View, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
+    @Thunk final ArrayMap<Reorderable, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
 
     private boolean mItemPlacementDirty = false;
 
@@ -1868,10 +1868,11 @@
             boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
                     != null && !solution.intersectingViews.contains(child);
 
+
             LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            if (c != null && !skip) {
-                ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
-                        lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
+            if (c != null && !skip && (child instanceof Reorderable)) {
+                ReorderPreviewAnimation rha = new ReorderPreviewAnimation((Reorderable) child,
+                        mode, lp.cellX, lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
                 rha.animate();
             }
         }
@@ -1893,7 +1894,7 @@
     // Class which represents the reorder preview animations. These animations show that an item is
     // in a temporary state, and hint at where the item will return to.
     class ReorderPreviewAnimation {
-        final View child;
+        final Reorderable child;
         float finalDeltaX;
         float finalDeltaY;
         float initDeltaX;
@@ -1913,8 +1914,8 @@
         float animationProgress = 0;
         ValueAnimator a;
 
-        public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
-                int cellY1, int spanX, int spanY) {
+        public ReorderPreviewAnimation(Reorderable child, int mode, int cellX0, int cellY0,
+                int cellX1, int cellY1, int spanX, int spanY) {
             regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
             final int x0 = mTmpPoint[0];
             final int y0 = mTmpPoint[1];
@@ -1926,63 +1927,60 @@
 
             this.child = child;
             this.mode = mode;
+            finalDeltaX = 0;
+            finalDeltaY = 0;
 
-            // TODO issue!
-            setInitialAnimationValues(false);
-            finalScale = (mChildScale - (CHILD_DIVIDEND / child.getWidth())) * initScale;
-            finalDeltaX = initDeltaX;
-            finalDeltaY = initDeltaY;
+            child.getReorderOffset(mTmpPointF);
+            initDeltaX = mTmpPointF.x;
+            initDeltaY = mTmpPointF.y;
+            initScale = child.getReorderScale();
+            finalScale = mChildScale - (CHILD_DIVIDEND / child.getView().getWidth()) * initScale;
+
             int dir = mode == MODE_HINT ? -1 : 1;
             if (dX == dY && dX == 0) {
             } else {
                 if (dY == 0) {
-                    finalDeltaX += - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
+                    finalDeltaX = -dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
                 } else if (dX == 0) {
-                    finalDeltaY += - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
+                    finalDeltaY = -dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
                 } else {
                     double angle = Math.atan( (float) (dY) / dX);
-                    finalDeltaX += (int) (- dir * Math.signum(dX) *
-                            Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
-                    finalDeltaY += (int) (- dir * Math.signum(dY) *
-                            Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
+                    finalDeltaX = (int) (-dir * Math.signum(dX)
+                            * Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
+                    finalDeltaY = (int) (-dir * Math.signum(dY)
+                            * Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
                 }
             }
         }
 
-        void setInitialAnimationValues(boolean restoreOriginalValues) {
-            if (restoreOriginalValues) {
-                if (child instanceof LauncherAppWidgetHostView) {
-                    LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
-                    initScale = lahv.getScaleToFit();
-                    initDeltaX = lahv.getTranslationForCentering().x;
-                    initDeltaY = lahv.getTranslationForCentering().y;
-                } else {
-                    initScale = mChildScale;
-                    initDeltaX = 0;
-                    initDeltaY = 0;
-                }
-            } else {
-                initScale = child.getScaleX();
-                initDeltaX = child.getTranslationX();
-                initDeltaY = child.getTranslationY();
-            }
+        void setInitialAnimationValuesToBaseline() {
+            initScale = mChildScale;
+            initDeltaX = 0;
+            initDeltaY = 0;
         }
 
         void animate() {
-            boolean noMovement = (finalDeltaX == initDeltaX) && (finalDeltaY == initDeltaY);
+            boolean noMovement = (finalDeltaX == 0) && (finalDeltaY == 0);
 
             if (mShakeAnimators.containsKey(child)) {
                 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
-                oldAnimation.cancel();
                 mShakeAnimators.remove(child);
+
                 if (noMovement) {
-                    completeAnimationImmediately();
+                    // A previous animation for this item exists, and no new animation will exist.
+                    // Finish the old animation smoothly.
+                    oldAnimation.finishAnimation();
                     return;
+                } else {
+                    // A previous animation for this item exists, and a new one will exist. Stop
+                    // the old animation in its tracks, and proceed with the new one.
+                    oldAnimation.cancel();
                 }
             }
             if (noMovement) {
                 return;
             }
+
             ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS, 0, 1);
             a = va;
 
@@ -1999,7 +1997,7 @@
             va.addListener(new AnimatorListenerAdapter() {
                 public void onAnimationRepeat(Animator animation) {
                     // We make sure to end only after a full period
-                    setInitialAnimationValues(true);
+                    setInitialAnimationValuesToBaseline();
                     repeating = true;
                 }
             });
@@ -2012,11 +2010,9 @@
             float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress;
             float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
             float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
-            child.setTranslationX(x);
-            child.setTranslationY(y);
+            child.setReorderOffset(x, y);
             float s = animationProgress * finalScale + (1 - animationProgress) * initScale;
-            child.setScaleX(s);
-            child.setScaleY(s);
+            child.setReorderScale(s);
         }
 
         private void cancel() {
@@ -2025,27 +2021,27 @@
             }
         }
 
-        @Thunk void completeAnimationImmediately() {
+        /**
+         * Smoothly returns the item to its baseline position / scale
+         */
+        @Thunk void finishAnimation() {
             if (a != null) {
                 a.cancel();
             }
 
-            setInitialAnimationValues(true);
-            a = new PropertyListBuilder()
-                    .scale(initScale)
-                    .translationX(initDeltaX)
-                    .translationY(initDeltaY)
-                    .build(child)
-                    .setDuration(REORDER_ANIMATION_DURATION);
-            Launcher.cast(mActivity).getDragController().addFirstFrameAnimationHelper(a);
+            setInitialAnimationValuesToBaseline();
+            ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS,
+                    animationProgress, 0);
+            a = va;
             a.setInterpolator(DEACCEL_1_5);
+            a.setDuration(REORDER_ANIMATION_DURATION);
             a.start();
         }
     }
 
     private void completeAndClearReorderPreviewAnimations() {
         for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
-            a.completeAnimationImmediately();
+            a.finishAnimation();
         }
         mShakeAnimators.clear();
     }
diff --git a/src/com/android/launcher3/Reorderable.java b/src/com/android/launcher3/Reorderable.java
new file mode 100644
index 0000000..5112eaf
--- /dev/null
+++ b/src/com/android/launcher3/Reorderable.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import android.graphics.PointF;
+import android.view.View;
+
+public interface Reorderable {
+
+    /**
+     * Set the offset related to reorder hint and "bounce" animations
+     */
+    void setReorderOffset(float x, float y);
+
+    void getReorderOffset(PointF offset);
+
+    /**
+     * Set the scale related to reorder hint and "bounce" animations
+     */
+    void setReorderScale(float scale);
+    float getReorderScale();
+
+    /**
+     * Get the com.android.view related to this object
+     */
+    View getView();
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
index 3ee1293..f97eb28 100644
--- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -15,206 +15,90 @@
  */
 package com.android.launcher3.allapps;
 
-import com.android.launcher3.util.Thunk;
+import androidx.recyclerview.widget.LinearSmoothScroller;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
-import java.util.HashSet;
-import java.util.List;
+import com.android.launcher3.allapps.AlphabeticalAppsList.FastScrollSectionInfo;
 
-import androidx.recyclerview.widget.RecyclerView;
+public class AllAppsFastScrollHelper {
 
-public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallback {
+    private static final int NO_POSITION = -1;
 
-    private static final int INITIAL_TOUCH_SETTLING_DURATION = 100;
-    private static final int REPEAT_TOUCH_SETTLING_DURATION = 200;
+    private int mTargetFastScrollPosition = NO_POSITION;
 
     private AllAppsRecyclerView mRv;
-    private AlphabeticalAppsList mApps;
+    private ViewHolder mLastSelectedViewHolder;
 
-    // Keeps track of the current and targeted fast scroll section (the section to scroll to after
-    // the initial delay)
-    int mTargetFastScrollPosition = -1;
-    @Thunk String mCurrentFastScrollSection;
-    @Thunk String mTargetFastScrollSection;
-
-    // The settled states affect the delay before the fast scroll animation is applied
-    private boolean mHasFastScrollTouchSettled;
-    private boolean mHasFastScrollTouchSettledAtLeastOnce;
-
-    // Set of all views animated during fast scroll.  We keep track of these ourselves since there
-    // is no way to reset a view once it gets scrapped or recycled without other hacks
-    private HashSet<RecyclerView.ViewHolder> mTrackedFastScrollViews = new HashSet<>();
-
-    // Smooth fast-scroll animation frames
-    @Thunk int mFastScrollFrameIndex;
-    @Thunk final int[] mFastScrollFrames = new int[10];
-
-    /**
-     * This runnable runs a single frame of the smooth scroll animation and posts the next frame
-     * if necessary.
-     */
-    @Thunk Runnable mSmoothSnapNextFrameRunnable = new Runnable() {
-        @Override
-        public void run() {
-            if (mFastScrollFrameIndex < mFastScrollFrames.length) {
-                mRv.scrollBy(0, mFastScrollFrames[mFastScrollFrameIndex]);
-                mFastScrollFrameIndex++;
-                mRv.postOnAnimation(mSmoothSnapNextFrameRunnable);
-            }
-        }
-    };
-
-    /**
-     * This runnable updates the current fast scroll section to the target fastscroll section.
-     */
-    Runnable mFastScrollToTargetSectionRunnable = new Runnable() {
-        @Override
-        public void run() {
-            // Update to the target section
-            mCurrentFastScrollSection = mTargetFastScrollSection;
-            mHasFastScrollTouchSettled = true;
-            mHasFastScrollTouchSettledAtLeastOnce = true;
-            updateTrackedViewsFastScrollFocusState();
-        }
-    };
-
-    public AllAppsFastScrollHelper(AllAppsRecyclerView rv, AlphabeticalAppsList apps) {
+    public AllAppsFastScrollHelper(AllAppsRecyclerView rv) {
         mRv = rv;
-        mApps = apps;
-    }
-
-    public void onSetAdapter(AllAppsGridAdapter adapter) {
-        adapter.setBindViewCallback(this);
     }
 
     /**
      * Smooth scrolls the recycler view to the given section.
-     *
-     * @return whether the fastscroller can scroll to the new section.
      */
-    public boolean smoothScrollToSection(int scrollY, int availableScrollHeight,
-            AlphabeticalAppsList.FastScrollSectionInfo info) {
-        if (mTargetFastScrollPosition != info.fastScrollToItem.position) {
-            mTargetFastScrollPosition = info.fastScrollToItem.position;
-            smoothSnapToPosition(scrollY, availableScrollHeight, info);
-            return true;
+    public void smoothScrollToSection(FastScrollSectionInfo info) {
+        if (mTargetFastScrollPosition == info.fastScrollToItem.position) {
+            return;
         }
-        return false;
-    }
-
-    /**
-     * Smoothly snaps to a given position.  We do this manually by calculating the keyframes
-     * ourselves and animating the scroll on the recycler view.
-     */
-    private void smoothSnapToPosition(int scrollY, int availableScrollHeight,
-            AlphabeticalAppsList.FastScrollSectionInfo info) {
-        mRv.removeCallbacks(mSmoothSnapNextFrameRunnable);
-        mRv.removeCallbacks(mFastScrollToTargetSectionRunnable);
-
-        trackAllChildViews();
-        if (mHasFastScrollTouchSettled) {
-            // In this case, the user has already settled once (and the fast scroll state has
-            // animated) and they are just fine-tuning their section from the last section, so
-            // we should make it feel fast and update immediately.
-            mCurrentFastScrollSection = info.sectionName;
-            mTargetFastScrollSection = null;
-            updateTrackedViewsFastScrollFocusState();
-        } else {
-            // Otherwise, the user has scrubbed really far, and we don't want to distract the user
-            // with the flashing fast scroll state change animation in addition to the fast scroll
-            // section popup, so reset the views to normal, and wait for the touch to settle again
-            // before animating the fast scroll state.
-            mCurrentFastScrollSection = null;
-            mTargetFastScrollSection = info.sectionName;
-            mHasFastScrollTouchSettled = false;
-            updateTrackedViewsFastScrollFocusState();
-
-            // Delay scrolling to a new section until after some duration.  If the user has been
-            // scrubbing a while and makes multiple big jumps, then reduce the time needed for the
-            // fast scroll to settle so it doesn't feel so long.
-            mRv.postDelayed(mFastScrollToTargetSectionRunnable,
-                    mHasFastScrollTouchSettledAtLeastOnce ?
-                            REPEAT_TOUCH_SETTLING_DURATION :
-                            INITIAL_TOUCH_SETTLING_DURATION);
-        }
-
-        // Calculate the full animation from the current scroll position to the final scroll
-        // position, and then run the animation for the duration.  If we are scrolling to the
-        // first fast scroll section, then just scroll to the top of the list itself.
-        List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
-                mApps.getFastScrollerSections();
-        int newPosition = info.fastScrollToItem.position;
-        int newScrollY = fastScrollSections.size() > 0 && fastScrollSections.get(0) == info
-                        ? 0
-                        : Math.min(availableScrollHeight, mRv.getCurrentScrollY(newPosition, 0));
-        int numFrames = mFastScrollFrames.length;
-        int deltaY = newScrollY - scrollY;
-        float ySign = Math.signum(deltaY);
-        int step = (int) (ySign * Math.ceil((float) Math.abs(deltaY) / numFrames));
-        for (int i = 0; i < numFrames; i++) {
-            // TODO(winsonc): We can interpolate this as well.
-            mFastScrollFrames[i] = (int) (ySign * Math.min(Math.abs(step), Math.abs(deltaY)));
-            deltaY -= step;
-        }
-        mFastScrollFrameIndex = 0;
-        mRv.postOnAnimation(mSmoothSnapNextFrameRunnable);
+        mTargetFastScrollPosition = info.fastScrollToItem.position;
+        mRv.getLayoutManager().startSmoothScroll(new MyScroller(mTargetFastScrollPosition));
     }
 
     public void onFastScrollCompleted() {
-        // TODO(winsonc): Handle the case when the user scrolls and releases before the animation
-        //                runs
-
-        // Stop animating the fast scroll position and state
-        mRv.removeCallbacks(mSmoothSnapNextFrameRunnable);
-        mRv.removeCallbacks(mFastScrollToTargetSectionRunnable);
-
-        // Reset the tracking variables
-        mHasFastScrollTouchSettled = false;
-        mHasFastScrollTouchSettledAtLeastOnce = false;
-        mCurrentFastScrollSection = null;
-        mTargetFastScrollSection = null;
-        mTargetFastScrollPosition = -1;
-
-        updateTrackedViewsFastScrollFocusState();
-        mTrackedFastScrollViews.clear();
+        mTargetFastScrollPosition = NO_POSITION;
+        setLastHolderSelected(false);
+        mLastSelectedViewHolder = null;
     }
 
-    @Override
-    public void onBindView(AllAppsGridAdapter.ViewHolder holder) {
-        // Update newly bound views to the current fast scroll state if we are fast scrolling
-        if (mCurrentFastScrollSection != null || mTargetFastScrollSection != null) {
-            mTrackedFastScrollViews.add(holder);
+
+    private void setLastHolderSelected(boolean isSelected) {
+        if (mLastSelectedViewHolder != null) {
+            mLastSelectedViewHolder.itemView.setActivated(isSelected);
+            mLastSelectedViewHolder.setIsRecyclable(!isSelected);
         }
     }
 
-    /**
-     * Starts tracking all the recycler view's children which are FastScrollFocusableViews.
-     */
-    private void trackAllChildViews() {
-        int childCount = mRv.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            RecyclerView.ViewHolder viewHolder = mRv.getChildViewHolder(mRv.getChildAt(i));
-            if (viewHolder != null) {
-                mTrackedFastScrollViews.add(viewHolder);
-            }
-        }
-    }
+    private class MyScroller extends LinearSmoothScroller {
 
-    /**
-     * Updates the fast scroll focus on all the children.
-     */
-    private void updateTrackedViewsFastScrollFocusState() {
-        for (RecyclerView.ViewHolder viewHolder : mTrackedFastScrollViews) {
-            int pos = viewHolder.getAdapterPosition();
-            boolean isActive = false;
-            if (mCurrentFastScrollSection != null
-                    && pos > RecyclerView.NO_POSITION
-                    && pos < mApps.getAdapterItems().size()) {
-                AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(pos);
-                isActive = item != null &&
-                        mCurrentFastScrollSection.equals(item.sectionName) &&
-                        item.position == mTargetFastScrollPosition;
+        private final int mTargetPosition;
+
+        public MyScroller(int targetPosition) {
+            super(mRv.getContext());
+
+            mTargetPosition = targetPosition;
+            setTargetPosition(targetPosition);
+        }
+
+        @Override
+        protected int getVerticalSnapPreference() {
+            return SNAP_TO_START;
+        }
+
+        @Override
+        protected void onStop() {
+            super.onStop();
+            if (mTargetPosition != mTargetFastScrollPosition) {
+                // Target changed, before the last scroll can finish
+                return;
             }
-            viewHolder.itemView.setActivated(isActive);
+
+            ViewHolder currentHolder = mRv.findViewHolderForAdapterPosition(mTargetPosition);
+            if (currentHolder == mLastSelectedViewHolder) {
+                return;
+            }
+
+            setLastHolderSelected(false);
+            mLastSelectedViewHolder = currentHolder;
+            setLastHolderSelected(true);
+        }
+
+        @Override
+        protected void onStart() {
+            super.onStart();
+            if (mTargetPosition != mTargetFastScrollPosition) {
+                setLastHolderSelected(false);
+                mLastSelectedViewHolder = null;
+            }
         }
     }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 4aebec0..3afa756 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -71,11 +71,6 @@
     public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
     public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
 
-
-    public interface BindViewCallback {
-        void onBindView(ViewHolder holder);
-    }
-
     /**
      * ViewHolder for each icon.
      */
@@ -186,7 +181,6 @@
 
     private int mAppsPerRow;
 
-    private BindViewCallback mBindViewCallback;
     private OnFocusChangeListener mIconFocusListener;
 
     // The text to show when there are no search results and no market search handler.
@@ -248,13 +242,6 @@
     }
 
     /**
-     * Sets the callback for when views are bound.
-     */
-    public void setBindViewCallback(BindViewCallback cb) {
-        mBindViewCallback = cb;
-    }
-
-    /**
      * Returns the grid layout manager.
      */
     public GridLayoutManager getLayoutManager() {
@@ -319,9 +306,6 @@
                 // nothing to do
                 break;
         }
-        if (mBindViewCallback != null) {
-            mBindViewCallback.onBindView(holder);
-        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 069472f..cbf02b7 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -53,12 +53,12 @@
 public class AllAppsRecyclerView extends BaseRecyclerView implements LogContainerProvider {
 
     private AlphabeticalAppsList mApps;
-    private AllAppsFastScrollHelper mFastScrollHelper;
     private final int mNumAppsPerRow;
 
     // The specific view heights that we use to calculate scroll
-    private SparseIntArray mViewHeights = new SparseIntArray();
-    private SparseIntArray mCachedScrollPositions = new SparseIntArray();
+    private final SparseIntArray mViewHeights = new SparseIntArray();
+    private final SparseIntArray mCachedScrollPositions = new SparseIntArray();
+    private final AllAppsFastScrollHelper mFastScrollHelper;
 
     // The empty-search result background
     private AllAppsBackgroundDrawable mEmptySearchBackground;
@@ -85,6 +85,7 @@
         mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
                 R.dimen.all_apps_empty_search_bg_top_offset);
         mNumAppsPerRow = LauncherAppState.getIDP(context).numColumns;
+        mFastScrollHelper = new AllAppsFastScrollHelper(this);
     }
 
     /**
@@ -92,7 +93,6 @@
      */
     public void setApps(AlphabeticalAppsList apps) {
         mApps = apps;
-        mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
     }
 
     public AlphabeticalAppsList getApps() {
@@ -221,9 +221,6 @@
             return "";
         }
 
-        // Stop the scroller if it is scrolling
-        stopScroll();
-
         // Find the fastscroll section that maps to this touch fraction
         List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
                 mApps.getFastScrollerSections();
@@ -236,10 +233,7 @@
             lastInfo = info;
         }
 
-        // Update the fast scroll
-        int scrollY = getCurrentScrollY();
-        int availableScrollHeight = getAvailableScrollHeight();
-        mFastScrollHelper.smoothScrollToSection(scrollY, availableScrollHeight, lastInfo);
+        mFastScrollHelper.smoothScrollToSection(lastInfo);
         return lastInfo.sectionName;
     }
 
@@ -257,7 +251,6 @@
                 mCachedScrollPositions.clear();
             }
         });
-        mFastScrollHelper.onSetAdapter((AllAppsGridAdapter) adapter);
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index fc29a30..0648682 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -172,8 +172,7 @@
         if (withDelay) {
             new Handler().postDelayed(() -> showForOverviewIfNeeded(launcher, false), DELAY_MS);
             return;
-        } else if (Launcher.ACTIVITY_TRACKER.hasPending()
-                || AbstractFloatingView.getTopOpenView(launcher) != null) {
+        } else if (AbstractFloatingView.getTopOpenView(launcher) != null) {
             // TODO: Move these checks to the top and call this method after invalidate handler.
             return;
         }
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 737c97b..1d32d1d 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -75,9 +75,6 @@
     }
 
     public static void sendScrollFinishedEventToTest(Context context) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "sendScrollFinishedEventToTest");
-        }
         final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
         if (accessibilityManager == null) return;
 
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index bcd91da..2b91cb1 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -168,6 +168,7 @@
             for (DebugFlag flag : sDebugFlags) {
                 flag.initialize(context);
             }
+            sDebugFlags.sort((f1, f2) -> f1.key.compareToIgnoreCase(f2.key));
         }
     }
 
@@ -178,10 +179,20 @@
     }
 
     public static void dump(PrintWriter pw) {
-        pw.println("FeatureFlags:");
+        pw.println("DeviceFlags:");
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
-                pw.println("  " + flag.key + "=" + flag.get());
+                if (flag instanceof DeviceFlag) {
+                    pw.println("  " + flag.toString());
+                }
+            }
+        }
+        pw.println("DebugFlags:");
+        synchronized (sDebugFlags) {
+            for (DebugFlag flag : sDebugFlags) {
+                if (!(flag instanceof DeviceFlag)) {
+                    pw.println("  " + flag.toString());
+                }
             }
         }
     }
@@ -202,13 +213,11 @@
 
         @Override
         public String toString() {
-            return appendProps(new StringBuilder()
-                    .append(getClass().getSimpleName()).append('{'))
-                    .append('}').toString();
+            return appendProps(new StringBuilder()).toString();
         }
 
         protected StringBuilder appendProps(StringBuilder src) {
-            return src.append("key=").append(key).append(", defaultValue=").append(defaultValue);
+            return src.append(key).append(", defaultValue=").append(defaultValue);
         }
 
         public void addChangeListener(Context context, Runnable r) { }
@@ -240,8 +249,7 @@
 
         @Override
         protected StringBuilder appendProps(StringBuilder src) {
-            return super.appendProps(src).append(", mCurrentValue=").append(mCurrentValue)
-                    .append(", description=").append(description);
+            return super.appendProps(src).append(", mCurrentValue=").append(mCurrentValue);
         }
     }
 
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 6c40b8a..0df6713 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -170,16 +170,14 @@
             }
         }, null, View.DRAG_FLAG_GLOBAL);
 
-
-        Intent homeIntent = listener.addToIntent(
-                new Intent(Intent.ACTION_MAIN)
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN)
                         .addCategory(Intent.CATEGORY_HOME)
                         .setPackage(getPackageName())
-                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-
-        Launcher.ACTIVITY_TRACKER.schedule(listener);
+                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        Launcher.ACTIVITY_TRACKER.runCallbackWhenActivityExists(listener, homeIntent);
         startActivity(homeIntent,
-                ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
+                ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out)
+                        .toBundle());
         mFinishOnPause = true;
         return false;
     }
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index e47a16f..707fd06 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -161,7 +161,6 @@
     }
 
     protected void postCleanup() {
-        Launcher.ACTIVITY_TRACKER.clearReference(this);
         if (mLauncher != null) {
             // Remove any drag params from the launcher intent since the drag operation is complete.
             Intent newIntent = new Intent(mLauncher.getIntent());
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 4fe1d1a..12d88df 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -164,6 +164,7 @@
     @Thunk final ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
 
     private AnimatorSet mCurrentAnimator;
+    private boolean mIsAnimatingClosed = false;
 
     protected final Launcher mLauncher;
     protected DragController mDragController;
@@ -729,15 +730,24 @@
     }
 
     private void animateClosed() {
+        if (mIsAnimatingClosed) {
+            return;
+        }
         if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
             mCurrentAnimator.cancel();
         }
         AnimatorSet a = new FolderAnimationManager(this, false /* isOpening */).getAnimator();
         a.addListener(new AnimatorListenerAdapter() {
             @Override
+            public void onAnimationStart(Animator animation) {
+                mIsAnimatingClosed = true;
+            }
+
+            @Override
             public void onAnimationEnd(Animator animation) {
                 closeComplete(true);
                 announceAccessibilityChanges();
+                mIsAnimatingClosed = false;
             }
         });
         startAnimation(a);
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index e29971e..96bdc2a 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -26,6 +26,7 @@
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
@@ -49,6 +50,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.OnAlarmListener;
 import com.android.launcher3.R;
+import com.android.launcher3.Reorderable;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.Interpolators;
@@ -79,7 +81,7 @@
  * An icon that can appear on in the workspace representing an {@link Folder}.
  */
 public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
-        DraggableView {
+        DraggableView, Reorderable {
 
     @Thunk ActivityContext mActivity;
     @Thunk Folder mFolder;
@@ -119,6 +121,9 @@
     private float mDotScale;
     private Animator mDotScaleAnim;
 
+    private final PointF mTranslationForReorder = new PointF(0, 0);
+    private float mScaleForReorder = 1f;
+
     private static final Property<FolderIcon, Float> DOT_SCALE_PROPERTY
             = new Property<FolderIcon, Float>(Float.TYPE, "dotScale") {
         @Override
@@ -226,16 +231,6 @@
         mBackground.getBounds(outBounds);
     }
 
-    @Override
-    public int getViewType() {
-        return DRAGGABLE_ICON;
-    }
-
-    @Override
-    public void getVisualDragBounds(Rect bounds) {
-        getPreviewBounds(bounds);
-    }
-
     public float getBackgroundStrokeWidth() {
         return mBackground.getStrokeWidth();
     }
@@ -716,4 +711,39 @@
     public void onFolderClose(int currentPage) {
         mPreviewItemManager.onFolderClose(currentPage);
     }
+
+
+    public void setReorderOffset(float x, float y) {
+        mTranslationForReorder.set(x, y);
+        super.setTranslationX(x);
+        super.setTranslationY(y);
+    }
+
+    public void getReorderOffset(PointF offset) {
+        offset.set(mTranslationForReorder);
+    }
+
+    public void setReorderScale(float scale) {
+        mScaleForReorder = scale;
+        super.setScaleX(scale);
+        super.setScaleY(scale);
+    }
+
+    public float getReorderScale() {
+        return mScaleForReorder;
+    }
+
+    public View getView() {
+        return this;
+    }
+
+    @Override
+    public int getViewType() {
+        return DRAGGABLE_ICON;
+    }
+
+    @Override
+    public void getVisualDragBounds(Rect bounds) {
+        getPreviewBounds(bounds);
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderNameInfo.java b/src/com/android/launcher3/folder/FolderNameInfo.java
index 1287219..1841cd9 100644
--- a/src/com/android/launcher3/folder/FolderNameInfo.java
+++ b/src/com/android/launcher3/folder/FolderNameInfo.java
@@ -50,6 +50,10 @@
         return mLabel;
     }
 
+    public double getScore() {
+        return mScore;
+    }
+
     /**
      * Used to package this object into a {@link Parcel}.
      *
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 2e0521f..3640d8b 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -15,47 +15,44 @@
  */
 package com.android.launcher3.states;
 
-import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
 
-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;
 import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.Handler;
 import android.provider.Settings;
+import android.util.Log;
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.UiThreadHelper;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Utility class to manage launcher rotation
  */
 public class RotationHelper implements OnSharedPreferenceChangeListener {
 
+    private static final String TAG = "RotationHelper";
+
     public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
 
     public static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform";
     private final ContentResolver mContentResolver;
+    private boolean mSystemAutoRotateEnabled;
 
-    /**
-     * Listener to receive changes when {@link #FIXED_ROTATION_TRANSFORM_SETTING_NAME} flag changes.
-     */
-    public interface ForcedRotationChangedListener {
-        void onForcedRotationChanged(boolean isForcedRotation);
-    }
+    private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
+        @Override
+        public void onChange(boolean selfChange) {
+            updateAutoRotateSetting();
+        }
+    };
 
     public static boolean getAllowRotationDefaultValue() {
         // If the device's pixel density was scaled (usually via settings for A11y), use the
@@ -72,12 +69,9 @@
 
     private final Activity mActivity;
     private final SharedPreferences mSharedPrefs;
-    private final SharedPreferences mFeatureFlagsPrefs;
 
     private boolean mIgnoreAutoRotateSettings;
-    private boolean mAutoRotateEnabled;
-    private boolean mForcedRotation;
-    private List<ForcedRotationChangedListener> mForcedRotationChangedListeners = new ArrayList<>();
+    private boolean mHomeRotationEnabled;
 
     /**
      * Rotation request made by
@@ -108,67 +102,35 @@
         if (!mIgnoreAutoRotateSettings) {
             mSharedPrefs = Utilities.getPrefs(mActivity);
             mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
-            mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+            mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
                     getAllowRotationDefaultValue());
         } else {
             mSharedPrefs = null;
         }
 
         mContentResolver = activity.getContentResolver();
-        mFeatureFlagsPrefs = Utilities.getFeatureFlagsPrefs(mActivity);
-        mFeatureFlagsPrefs.registerOnSharedPreferenceChangeListener(this);
-        updateForcedRotation(true);
     }
 
-    /**
-     * @param setValueFromPrefs If true, then {@link #mForcedRotation} will get set to the value
-     *                          from the home developer settings. Otherwise it will not.
-     *                          This is primarily to allow tests to set their own conditions.
-     */
-    private void updateForcedRotation(boolean setValueFromPrefs) {
-        boolean isForcedRotation = mFeatureFlagsPrefs
-                .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true)
-                && !getAllowRotationDefaultValue();
-        if (mForcedRotation == isForcedRotation) {
-            return;
+    private void updateAutoRotateSetting() {
+        int autoRotateEnabled = 0;
+        try {
+            autoRotateEnabled = Settings.System.getInt(mContentResolver,
+                    Settings.System.ACCELEROMETER_ROTATION);
+        } catch (Settings.SettingNotFoundException e) {
+            Log.e(TAG, "autorotate setting not found", e);
         }
-        if (setValueFromPrefs) {
-            mForcedRotation = isForcedRotation;
-        }
-        UI_HELPER_EXECUTOR.execute(() -> {
-            if (mActivity.checkSelfPermission(WRITE_SECURE_SETTINGS) == PERMISSION_GRANTED) {
-                Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME,
-                            mForcedRotation ? 1 : 0);
-            }
-        });
-        for (ForcedRotationChangedListener listener : mForcedRotationChangedListeners) {
-            listener.onForcedRotationChanged(mForcedRotation);
-        }
-    }
 
-    /**
-     * will not be called when first registering the listener.
-     */
-    public void addForcedRotationCallback(ForcedRotationChangedListener listener) {
-        mForcedRotationChangedListeners.add(listener);
-    }
-
-    public void removeForcedRotationCallback(ForcedRotationChangedListener listener) {
-        mForcedRotationChangedListeners.remove(listener);
+        mSystemAutoRotateEnabled = autoRotateEnabled == 1;
     }
 
     @Override
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
-        if (FLAG_ENABLE_FIXED_ROTATION_TRANSFORM.equals(s)) {
-            updateForcedRotation(true);
-            return;
-        }
-
-        boolean wasRotationEnabled = mAutoRotateEnabled;
-        mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+        boolean wasRotationEnabled = mHomeRotationEnabled;
+        mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
                 getAllowRotationDefaultValue());
-        if (mAutoRotateEnabled != wasRotationEnabled) {
+        if (mHomeRotationEnabled != wasRotationEnabled) {
             notifyChange();
+            updateAutoRotateSetting();
         }
     }
 
@@ -197,10 +159,6 @@
     public void forceAllowRotationForTesting(boolean allowRotation) {
         mIgnoreAutoRotateSettings =
                 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;
-        updateForcedRotation(false);
         notifyChange();
     }
 
@@ -208,6 +166,11 @@
         if (!mInitialized) {
             mInitialized = true;
             notifyChange();
+
+            mContentResolver.registerContentObserver(
+                    Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
+                    false, mSystemAutoRotateObserver);
+            updateAutoRotateSetting();
         }
     }
 
@@ -217,8 +180,7 @@
             if (mSharedPrefs != null) {
                 mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
             }
-            mForcedRotationChangedListeners.clear();
-            mFeatureFlagsPrefs.unregisterOnSharedPreferenceChangeListener(this);
+            mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver);
         }
     }
 
@@ -228,10 +190,7 @@
         }
 
         final int activityFlags;
-        if (mForcedRotation) {
-            // TODO(b/150214193) Properly address this
-            activityFlags = SCREEN_ORIENTATION_PORTRAIT;
-        } else if (mStateHandlerRequest != REQUEST_NONE) {
+        if (mStateHandlerRequest != REQUEST_NONE) {
             activityFlags = mStateHandlerRequest == REQUEST_LOCK ?
                     SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
         } else if (mCurrentTransitionRequest != REQUEST_NONE) {
@@ -240,7 +199,7 @@
         } else if (mCurrentStateRequest == REQUEST_LOCK) {
             activityFlags = SCREEN_ORIENTATION_LOCKED;
         } else if (mIgnoreAutoRotateSettings || mCurrentStateRequest == REQUEST_ROTATE
-                || mAutoRotateEnabled) {
+                || mHomeRotationEnabled) {
             activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
         } else {
             // If auto rotation is off, allow rotation on the activity, in case the user is using
@@ -253,11 +212,23 @@
         }
     }
 
+    /**
+     * @return how many factors {@param newRotation} is rotated 90 degrees clockwise.
+     * E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise...
+     * A value of 0 means no rotation has been applied
+     */
+    public static int deltaRotation(int oldRotation, int newRotation) {
+        int delta = newRotation - oldRotation;
+        if (delta < 0) delta += 4;
+        return delta;
+    }
+
     @Override
     public String toString() {
         return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d,"
-                + " mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, mAutoRotateEnabled=%b]",
+                + " mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, mHomeRotationEnabled=%b,"
+                        + " mSystemAutoRotateEnabled=%b]",
                 mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags,
-                mIgnoreAutoRotateSettings, mAutoRotateEnabled);
+                mIgnoreAutoRotateSettings, mHomeRotationEnabled, mSystemAutoRotateEnabled);
     }
 }
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index e7449bb..82f2eb4 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -98,5 +98,4 @@
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
 
     public static final String APP_NOT_DISABLED = "b/139891609";
-    public static final String NO_SCROLL_END_WIDGETS = "b/152354290";
 }
diff --git a/src/com/android/launcher3/touch/HomeRotatedPageHandler.java b/src/com/android/launcher3/touch/HomeRotatedPageHandler.java
new file mode 100644
index 0000000..710b676
--- /dev/null
+++ b/src/com/android/launcher3/touch/HomeRotatedPageHandler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.touch;
+
+import android.graphics.RectF;
+import android.view.Surface;
+
+public class HomeRotatedPageHandler extends PortraitPagedViewHandler {
+    @Override
+    public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
+        if (launcherRotation == Surface.ROTATION_0) {
+            super.offsetTaskRect(rect, value, displayRotation, launcherRotation);
+        } else if (launcherRotation == Surface.ROTATION_90) {
+            if (displayRotation == Surface.ROTATION_0) {
+                rect.offset(0, value);
+            } else if (displayRotation == Surface.ROTATION_90) {
+                rect.offset(value, 0);
+            } else if (displayRotation == Surface.ROTATION_180) {
+                rect.offset(-value, 0);
+            } else {
+                rect.offset(-value, 0);
+            }
+        } else if (launcherRotation == Surface.ROTATION_270) {
+            if (displayRotation == Surface.ROTATION_0) {
+                rect.offset(0, -value);
+            } else if (displayRotation == Surface.ROTATION_90) {
+                rect.offset(value, 0);
+            } else if (displayRotation == Surface.ROTATION_180) {
+                rect.offset(0, -value);
+            } else {
+                rect.offset(value, 0);
+            }
+        } // TODO (b/149609488) handle 180 case as well
+    }
+}
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index bab5747..f9f3bf4 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -78,6 +78,11 @@
     }
 
     @Override
+    public boolean isLayoutNaturalToLauncher() {
+        return false;
+    }
+
+    @Override
     public void adjustFloatingIconStartVelocity(PointF velocity) {
         float oldX = velocity.x;
         float oldY = velocity.y;
@@ -182,7 +187,7 @@
     }
 
     @Override
-    public void offsetTaskRect(RectF rect, float value, int displayRotation) {
+    public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
         if (displayRotation == Surface.ROTATION_0) {
             rect.offset(0, value);
         } else if (displayRotation == Surface.ROTATION_90) {
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 50606ec..ba4c064 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -42,6 +42,7 @@
     PagedOrientationHandler PORTRAIT = new PortraitPagedViewHandler();
     PagedOrientationHandler LANDSCAPE = new LandscapePagedViewHandler();
     PagedOrientationHandler SEASCAPE = new SeascapePagedViewHandler();
+    PagedOrientationHandler HOME_ROTATED = new HomeRotatedPageHandler();
 
     interface Int2DAction<T> {
         void call(T target, int x, int y);
@@ -79,7 +80,7 @@
     void setMaxScroll(AccessibilityEvent event, int maxScroll);
     boolean getRecentsRtlSetting(Resources resources);
     float getDegreesRotated();
-    void offsetTaskRect(RectF rect, float value, int delta);
+    void offsetTaskRect(RectF rect, float value, int delta, int launcherRotation);
     int getPrimaryValue(int x, int y);
     int getSecondaryValue(int x, int y);
     void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll);
@@ -89,6 +90,7 @@
     void scrollerStartScroll(OverScroller scroller, int newPosition);
     void getCurveProperties(PagedView view, Rect insets, CurveProperties out);
     boolean isGoingUp(float displacement);
+    boolean isLayoutNaturalToLauncher();
 
     /**
      * Maps the velocity from the coordinate plane of the foreground app to that
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 245138f..7c44eba 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -78,6 +78,11 @@
     }
 
     @Override
+    public boolean isLayoutNaturalToLauncher() {
+        return true;
+    }
+
+    @Override
     public void adjustFloatingIconStartVelocity(PointF velocity) {
         //no-op
     }
@@ -180,7 +185,7 @@
     }
 
     @Override
-    public void offsetTaskRect(RectF rect, float value, int displayRotation) {
+    public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
         if (displayRotation == Surface.ROTATION_0) {
             rect.offset(value, 0);
         } else if (displayRotation == Surface.ROTATION_90) {
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index eebd87f..5ce8a57 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -36,7 +36,7 @@
     }
 
     @Override
-    public void offsetTaskRect(RectF rect, float value, int displayRotation) {
+    public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
         if (displayRotation == Surface.ROTATION_0) {
             rect.offset(0, value);
         } else if (displayRotation == Surface.ROTATION_90) {
diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java
index 499f655..59266b4 100644
--- a/src/com/android/launcher3/util/ActivityTracker.java
+++ b/src/com/android/launcher3/util/ActivityTracker.java
@@ -15,27 +15,23 @@
  */
 package com.android.launcher3.util;
 
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.util.Log;
 
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseActivity;
-import com.android.launcher3.testing.TestProtocol;
 
 import java.lang.ref.WeakReference;
 
 /**
  * Helper class to statically track activity creation
+ * @param <T> The activity type to track
  */
-public final class ActivityTracker<T extends BaseActivity> implements Runnable {
+public final class ActivityTracker<T extends BaseActivity> {
 
     private WeakReference<T> mCurrentActivity = new WeakReference<>(null);
-    private WeakReference<SchedulerCallback<T>> mPendingCallback = new WeakReference<>(null);
 
     private static final String EXTRA_SCHEDULER_CALLBACK = "launcher.scheduler_callback";
 
@@ -51,76 +47,32 @@
     }
 
     /**
-     * Schedules the callback to be notified when the activity is created.
-     * @return true if the activity is already created, false otherwise
+     * Call {@link SchedulerCallback#init(BaseActivity, boolean)} when the activity is ready.
+     * If the activity is already created, this is called immediately, otherwise we add the
+     * callback as an extra on the intent, and will call init() when we get handleIntent().
+     * @param callback The callback to call init() on when the activity is ready.
+     * @param intent The intent that will be used to initialize the activity, if the activity
+     *               doesn't already exist. We add the callback as an extra on this intent.
      */
-    public boolean schedule(SchedulerCallback<? extends T> callback) {
-        synchronized (this) {
-            mPendingCallback = new WeakReference<>((SchedulerCallback<T>) callback);
-        }
-        if (!notifyInitIfPending()) {
-            // If the activity doesn't already exist, then post and wait for the activity to start
-            MAIN_EXECUTOR.execute(this);
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    public void run() {
-        notifyInitIfPending();
-    }
-
-    /**
-     * Notifies the pending callback if the activity is now created.
-     * @return true if the activity is now created.
-     */
-    private boolean notifyInitIfPending() {
+    public void runCallbackWhenActivityExists(SchedulerCallback<T> callback, Intent intent) {
         T activity = mCurrentActivity.get();
         if (activity != null) {
-            notifyInitIfPending(activity, activity.isStarted());
-            return true;
+            callback.init(activity, activity.isStarted());
+        } else {
+            callback.addToIntent(intent);
         }
-        return false;
-    }
-
-    public boolean notifyInitIfPending(T activity, boolean alreadyOnHome) {
-        SchedulerCallback<T> pendingCallback = mPendingCallback.get();
-        if (pendingCallback != null) {
-            if (!pendingCallback.init(activity, alreadyOnHome)) {
-                clearReference(pendingCallback);
-            }
-            return true;
-        }
-        return false;
-    }
-
-    public boolean clearReference(SchedulerCallback<? extends T> handler) {
-        synchronized (this) {
-            if (mPendingCallback.get() == handler) {
-                mPendingCallback.clear();
-                return true;
-            }
-            return false;
-        }
-    }
-
-    public boolean hasPending() {
-        return mPendingCallback.get() != null;
     }
 
     public boolean handleCreate(T activity) {
         mCurrentActivity = new WeakReference<>(activity);
-        return handleIntent(activity, activity.getIntent(), false, false);
+        return handleIntent(activity, activity.getIntent(), false);
     }
 
     public boolean handleNewIntent(T activity, Intent intent) {
-        return handleIntent(activity, intent, activity.isStarted(), true);
+        return handleIntent(activity, intent, activity.isStarted());
     }
 
-    private boolean handleIntent(
-            T activity, Intent intent, boolean alreadyOnHome, boolean explicitIntent) {
-        boolean result = false;
+    private boolean handleIntent(T activity, Intent intent, boolean alreadyOnHome) {
         if (intent != null && intent.getExtras() != null) {
             IBinder stateBinder = intent.getExtras().getBinder(EXTRA_SCHEDULER_CALLBACK);
             if (stateBinder instanceof ObjectWrapper) {
@@ -129,19 +81,26 @@
                 if (!handler.init(activity, alreadyOnHome)) {
                     intent.getExtras().remove(EXTRA_SCHEDULER_CALLBACK);
                 }
-                result = true;
+                return true;
             }
         }
-        if (!result && !explicitIntent) {
-            result = notifyInitIfPending(activity, alreadyOnHome);
-        }
-        return result;
+        return false;
     }
 
     public interface SchedulerCallback<T extends BaseActivity> {
 
+        /**
+         * Called when the activity is ready.
+         * @param alreadyOnHome Whether the activity is already started.
+         * @return Whether to continue receiving callbacks (i.e. if the activity is recreated).
+         */
         boolean init(T activity, boolean alreadyOnHome);
 
+        /**
+         * Adds this callback as an extra on the intent, so we can retrieve it in handleIntent() and
+         * call {@link #init}. The intent should be used to start the activity after calling this
+         * method in order for us to get handleIntent().
+         */
         default Intent addToIntent(Intent intent) {
             Bundle extras = new Bundle();
             extras.putBinder(EXTRA_SCHEDULER_CALLBACK, ObjectWrapper.wrap(this));
diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java
index 07f835d..a8642b0 100644
--- a/src/com/android/launcher3/util/MultiValueAlpha.java
+++ b/src/com/android/launcher3/util/MultiValueAlpha.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3.util;
 
-import android.util.Property;
+import android.util.FloatProperty;
 import android.view.View;
 
 import java.util.Arrays;
@@ -26,8 +26,8 @@
  */
 public class MultiValueAlpha {
 
-    public static final Property<AlphaProperty, Float> VALUE =
-            new Property<AlphaProperty, Float>(Float.TYPE, "value") {
+    public static final FloatProperty<AlphaProperty> VALUE =
+            new FloatProperty<AlphaProperty>("value") {
 
                 @Override
                 public Float get(AlphaProperty alphaProperty) {
@@ -35,7 +35,7 @@
                 }
 
                 @Override
-                public void set(AlphaProperty object, Float value) {
+                public void setValue(AlphaProperty object, float value) {
                     object.setValue(value);
                 }
             };
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 2fc3eaf..6915953 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -275,9 +275,6 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "BaseDragLayer: " + ev);
-        }
         switch (ev.getAction()) {
             case ACTION_DOWN: {
                 mTouchDispatchState |= TOUCH_DISPATCHING_VIEW;
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index e114cf8..5b3840f 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -124,7 +124,6 @@
         super.onAttachedToWindow();
         if (!mIsOpening) {
             getViewTreeObserver().addOnGlobalLayoutListener(this);
-            mLauncher.getRotationHelper().setCurrentTransitionRequest(REQUEST_LOCK);
         }
     }
 
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 5653801..6a83332 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -67,7 +67,6 @@
 
     private final static int MAX_TRACK_ALPHA = 30;
     private final static int SCROLL_BAR_VIS_DURATION = 150;
-    private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 0.75f;
 
     private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
             Collections.singletonList(new Rect());
@@ -184,7 +183,7 @@
         if (mThumbOffsetY == y) {
             return;
         }
-        updatePopupY((int) y);
+        updatePopupY(y);
         mThumbOffsetY = y;
         invalidate();
     }
@@ -237,7 +236,7 @@
                 } else if (mRv.supportsFastScrolling()
                         && isNearScrollBar(mDownX)) {
                     calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY);
-                    updateFastScrollSectionNameAndThumbOffset(mLastY, y);
+                    updateFastScrollSectionNameAndThumbOffset(y);
                 }
                 break;
             case MotionEvent.ACTION_MOVE:
@@ -252,7 +251,7 @@
                     calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY);
                 }
                 if (mIsDragging) {
-                    updateFastScrollSectionNameAndThumbOffset(mLastY, y);
+                    updateFastScrollSectionNameAndThumbOffset(y);
                 }
                 break;
             case MotionEvent.ACTION_UP:
@@ -281,7 +280,7 @@
         showActiveScrollbar(true);
     }
 
-    private void updateFastScrollSectionNameAndThumbOffset(int lastY, int y) {
+    private void updateFastScrollSectionNameAndThumbOffset(int y) {
         // Update the fastscroller section name at this touch position
         int bottom = mRv.getScrollbarTrackHeight() - mThumbHeight;
         float boundedY = (float) Math.max(0, Math.min(bottom, y - mTouchOffsetY));
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 5d33f13..6f2e179 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -19,8 +19,6 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.graphics.PointF;
-import android.graphics.Rect;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.SparseBooleanArray;
@@ -40,7 +38,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.util.Executors;
@@ -51,7 +48,7 @@
  * {@inheritDoc}
  */
 public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
-        implements TouchCompleteListener, View.OnLongClickListener, DraggableView {
+        implements TouchCompleteListener, View.OnLongClickListener {
 
     // Related to the auto-advancing of widgets
     private static final long ADVANCE_INTERVAL = 20000;
@@ -73,15 +70,7 @@
     private boolean mIsAutoAdvanceRegistered;
     private Runnable mAutoAdvanceRunnable;
 
-    /**
-     * The scaleX and scaleY value such that the widget fits within its cellspans, scaleX = scaleY.
-     */
-    private float mScaleToFit = 1f;
 
-    /**
-     * The translation values to center the widget within its cellspans.
-     */
-    private final PointF mTranslationForCentering = new PointF(0, 0);
 
     public LauncherAppWidgetHostView(Context context) {
         super(context);
@@ -307,26 +296,6 @@
         scheduleNextAdvance();
     }
 
-    public void setScaleToFit(float scale) {
-        mScaleToFit = scale;
-        setScaleX(scale);
-        setScaleY(scale);
-    }
-
-    public float getScaleToFit() {
-        return mScaleToFit;
-    }
-
-    public void setTranslationForCentering(float x, float y) {
-        mTranslationForCentering.set(x, y);
-        setTranslationX(x);
-        setTranslationY(y);
-    }
-
-    public PointF getTranslationForCentering() {
-        return mTranslationForCentering;
-    }
-
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -357,17 +326,4 @@
         }
         return false;
     }
-
-    @Override
-    public int getViewType() {
-        return DRAGGABLE_WIDGET;
-    }
-
-    @Override
-    public void getVisualDragBounds(Rect bounds) {
-        int width = (int) (getMeasuredWidth() * mScaleToFit);
-        int height = (int) (getMeasuredHeight() * mScaleToFit);
-
-        bounds.set(0, 0 , width, height);
-    }
 }
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index 104ad77..5ea01d9 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -18,12 +18,14 @@
 
 import android.appwidget.AppWidgetHostView;
 import android.content.Context;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 
+import com.android.launcher3.Reorderable;
 import com.android.launcher3.dragndrop.DraggableView;
 
 import java.util.ArrayList;
@@ -32,7 +34,20 @@
  * Extension of AppWidgetHostView with support for controlled keyboard navigation.
  */
 public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
-        implements DraggableView {
+        implements DraggableView, Reorderable {
+
+    /**
+     * The scaleX and scaleY value such that the widget fits within its cellspans, scaleX = scaleY.
+     */
+    private float mScaleToFit = 1f;
+
+    /**
+     * The translation values to center the widget within its cellspans.
+     */
+    private final PointF mTranslationForCentering = new PointF(0, 0);
+
+    private final PointF mTranslationForReorder = new PointF(0, 0);
+    private float mScaleForReorder = 1f;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mChildrenFocused;
@@ -137,6 +152,47 @@
         setSelected(childIsFocused);
     }
 
+    public void setScaleToFit(float scale) {
+        mScaleToFit = scale;
+        super.setScaleX(scale * mScaleForReorder);
+        super.setScaleY(scale * mScaleForReorder);
+    }
+
+    public float getScaleToFit() {
+        return mScaleToFit;
+    }
+
+    public View getView() {
+        return this;
+    }
+
+
+    public void setTranslationForCentering(float x, float y) {
+        mTranslationForCentering.set(x, y);
+        super.setTranslationX(x + mTranslationForReorder.x);
+        super.setTranslationY(y + mTranslationForReorder.y);
+    }
+
+    public void setReorderOffset(float x, float y) {
+        mTranslationForReorder.set(x, y);
+        super.setTranslationX(mTranslationForCentering.x + x);
+        super.setTranslationY(mTranslationForCentering.y + y);
+    }
+
+    public void getReorderOffset(PointF offset) {
+        offset.set(mTranslationForReorder);
+    }
+
+    public void setReorderScale(float scale) {
+        mScaleForReorder = scale;
+        super.setScaleX(mScaleToFit * scale);
+        super.setScaleY(mScaleToFit * scale);
+    }
+
+    public float getReorderScale() {
+        return mScaleForReorder;
+    }
+
     @Override
     public int getViewType() {
         return DRAGGABLE_WIDGET;
@@ -144,6 +200,9 @@
 
     @Override
     public void getVisualDragBounds(Rect bounds) {
-        bounds.set(0, 0 , getMeasuredWidth(), getMeasuredHeight());
+        int width = (int) (getMeasuredWidth() * mScaleToFit);
+        int height = (int) (getMeasuredHeight() * mScaleToFit);
+
+        bounds.set(0, 0 , width, height);
     }
 }
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index 37a30af..536b766 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -71,14 +71,6 @@
 
     }
 
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsFullSheet: " + ev);
-        }
-        return super.dispatchTouchEvent(ev);
-    }
-
     public WidgetsFullSheet(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 17baa27..82d4110 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -158,23 +158,13 @@
                     mScrollbar.isHitInParent(e.getX(), e.getY(), mFastScrollerOffset);
         }
         if (mTouchDownOnScroller) {
-            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 mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
         }
         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);
         }
@@ -182,31 +172,5 @@
 
     @Override
     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();
     }
 }
\ No newline at end of file