Merge "Apply localBounds / screenSpaceBounds for RemoteAnimationTarget in Launcher" into ub-launcher3-master
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 6d105ac..c7a0253 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -46,7 +46,9 @@
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
-
+    <!-- TODO(b/150802536): Enabled only for ENABLE_FIXED_ROTATION_TRANSFORM feature flag -->
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    
     <!--
     Permissions required for read/write access to the workspace data. These permission name
     should not conflict with that defined in other apps, as such an app should embed its package
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index d360613..1d0b045 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -26,6 +26,7 @@
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
+    
     <application
         android:backupAgent="com.android.launcher3.LauncherBackupAgent"
         android:fullBackupOnly="true"
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 7bc656a..95198b8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -20,7 +20,7 @@
 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL_COMPONENTS;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
@@ -220,7 +220,7 @@
                 LauncherStateManager stateManager = mLauncher.getStateManager();
                 return stateManager.createAtomicAnimation(
                         stateManager.getCurrentStableState(), OVERVIEW, builder,
-                        ANIM_ALL, ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW);
+                        ANIM_ALL_COMPONENTS, ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW);
             }
 
             default:
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 b5d8424..65aaf22 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
@@ -30,6 +30,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.quickstep.views.ClearAllButton;
@@ -64,20 +65,16 @@
             @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
         super.setStateWithAnimationInternal(toState, builder, config);
 
-        if (!toState.overviewUi) {
-            builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals);
-        }
-
+        ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1).setDuration(config.duration);
         if (toState.overviewUi) {
-            ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
-            updateAnim.addUpdateListener(valueAnimator -> {
-                // While animating into recents, update the visible task data as needed
-                mRecentsView.loadVisibleTaskData();
-            });
-            updateAnim.setDuration(config.duration);
-            builder.play(updateAnim);
+            // While animating into recents, update the visible task data as needed
+            updateAnim.addUpdateListener(valueAnimator -> mRecentsView.loadVisibleTaskData());
             mRecentsView.updateEmptyMessage();
+        } else {
+            updateAnim.addListener(
+                    AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
         }
+        builder.play(updateAnim);
 
         PropertySetter propertySetter = config.getPropertySetter(builder);
         setAlphas(propertySetter, toState.getVisibleElements(mLauncher));
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 9cbe11a..b347013 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -21,7 +21,8 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
-import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT;
+import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_PEEK;
+import static com.android.launcher3.LauncherStateManager.SKIP_OVERVIEW;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
@@ -43,6 +44,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppTransitionManagerImpl;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationFlags;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
@@ -104,7 +106,7 @@
         LauncherState toState = isPaused ? OVERVIEW_PEEK : NORMAL;
         long peekDuration = isPaused ? PEEK_IN_ANIM_DURATION : PEEK_OUT_ANIM_DURATION;
         mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(fromState, toState,
-                new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT, peekDuration);
+                new AnimatorSetBuilder(), PLAY_ATOMIC_OVERVIEW_PEEK, peekDuration);
         mPeekAnim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -210,7 +212,7 @@
                 super.onAnimationEnd(animation);
                 if (mCancelled) {
                     mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(mFromState,
-                            mToState, new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT,
+                            mToState, new AnimatorSetBuilder(), PLAY_ATOMIC_OVERVIEW_PEEK,
                             PEEK_OUT_ANIM_DURATION);
                     mPeekAnim.start();
                 }
@@ -237,11 +239,14 @@
     }
 
     @Override
-    protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) {
+    @AnimationFlags
+    protected int updateAnimComponentsOnReinit(@AnimationFlags int animComponents) {
         if (handlingOverviewAnim()) {
             // We don't want the state transition to all apps to animate overview,
             // as that will cause a jump after our atomic animation.
-            builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
+            return animComponents | SKIP_OVERVIEW;
+        } else {
+            return animComponents;
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 7bae211..7cebabe 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.LauncherState.HINT_STATE;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT;
+import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_PEEK;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
@@ -175,7 +175,7 @@
                 long duration = OVERVIEW.getTransitionDuration(mLauncher);
                 AnimatorSet anim = stateManager.createAtomicAnimation(
                         stateManager.getState(), NORMAL, builder,
-                        ATOMIC_OVERVIEW_PEEK_COMPONENT, duration);
+                        PLAY_ATOMIC_OVERVIEW_PEEK, duration);
                 anim.addListener(AnimationSuccessListener.forRunnable(
                         () -> onSwipeInteractionCompleted(NORMAL, Touch.SWIPE)));
                 anim.start();
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 52625dc..81a6d9b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -21,13 +21,13 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.QUICK_SWITCH;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL_COMPONENTS;
+import static com.android.launcher3.LauncherStateManager.SKIP_OVERVIEW;
 import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_5;
@@ -55,8 +55,8 @@
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.LauncherStateManager.AnimationFlags;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsTransitionController;
@@ -214,7 +214,7 @@
         nonOverviewBuilder.setInterpolator(ANIM_ALL_APPS_FADE, FADE_OUT_INTERPOLATOR);
         nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, TRANSLATE_OUT_INTERPOLATOR);
         nonOverviewBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, TRANSLATE_OUT_INTERPOLATOR);
-        updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder, ANIM_ALL);
+        updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder, ANIM_ALL_COMPONENTS);
         mNonOverviewAnim.dispatchOnStart();
 
         if (mRecentsView.getTaskViewCount() == 0) {
@@ -231,11 +231,10 @@
 
     /** Create state animation to control non-overview components. */
     private void updateNonOverviewAnim(LauncherState toState, AnimatorSetBuilder builder,
-            @LauncherStateManager.AnimationComponents int animComponents) {
-        builder.addFlag(FLAG_DONT_ANIMATE_OVERVIEW);
+            @AnimationFlags int animComponents) {
         long accuracy = (long) (Math.max(mXRange, mYRange) * 2);
         mNonOverviewAnim = mLauncher.getStateManager().createAnimationToNewWorkspace(toState,
-                builder, accuracy, this::clearState, animComponents);
+                builder, accuracy, this::clearState, animComponents | SKIP_OVERVIEW);
     }
 
     private void setupOverviewAnimators() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index d5b221d..8de1b3a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.QUICK_SWITCH;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL_COMPONENTS;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
@@ -39,7 +40,6 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
@@ -112,7 +112,7 @@
         setupInterpolators(animatorSetBuilder);
         long accuracy = (long) (getShiftRange() * 2);
         mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
-                animatorSetBuilder, accuracy, this::clearState, LauncherStateManager.ANIM_ALL);
+                animatorSetBuilder, accuracy, this::clearState, ANIM_ALL_COMPONENTS);
         mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator -> {
             updateFullscreenProgress((Float) valueAnimator.getAnimatedValue());
         });
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 9f19bb3..7786a8f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -79,6 +79,7 @@
     private static final String TAG = "BaseSwipeUpHandler";
     protected static final Rect TEMP_RECT = new Rect();
 
+    public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
     private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
 
     // The distance needed to drag to reach the task size in recents.
@@ -481,10 +482,6 @@
             public void onUpdate(RectF currentRect, float progress) {
                 homeAnim.setPlayFraction(progress);
 
-                mTransformParams.setProgress(
-                        Utilities.mapRange(progress, startTransformProgress, endTransformProgress))
-                        .setCurrentRect(currentRect)
-                        .setTargetAlpha(getWindowAlpha(progress));
                 rotatedRect.set(currentRect);
                 if (isFloatingIconView) {
                     RotationHelper.mapRectFromNormalOrientation(rotatedRect,
@@ -492,6 +489,10 @@
                     mTransformParams.setCornerRadius(endRadius * progress + startRadius
                         * (1f - progress));
                 }
+                mTransformParams.setProgress(
+                    Utilities.mapRange(progress, startTransformProgress, endTransformProgress))
+                    .setCurrentRect(rotatedRect)
+                    .setTargetAlpha(getWindowAlpha(progress));
                 mAppWindowAnimationHelper.applyTransform(mTransformParams);
 
                 if (isFloatingIconView) {
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 ce67457..ea5561b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -21,7 +21,6 @@
 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
-import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
 import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
@@ -97,7 +96,7 @@
 
     private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
 
-    private boolean mIsMotionPaused = false;
+    private boolean mOverviewThresholdPassed = false;
 
     private final boolean mInQuickSwitchMode;
     private final boolean mContinuingLastGesture;
@@ -222,10 +221,16 @@
 
     @Override
     public void onMotionPauseChanged(boolean isPaused) {
-        if (!mInQuickSwitchMode) {
-            mIsMotionPaused = isPaused;
+        if (!mInQuickSwitchMode && mDeviceState.isFullyGesturalNavMode()) {
+            updateOverviewThresholdPassed(isPaused);
+        }
+    }
+
+    private void updateOverviewThresholdPassed(boolean passed) {
+        if (passed != mOverviewThresholdPassed) {
+            mOverviewThresholdPassed = passed;
             if (mSwipeUpOverHome) {
-                mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1)
+                mLauncherAlpha.animateToValue(mLauncherAlpha.value, passed ? 0 : 1)
                         .setDuration(150).start();
             }
             performHapticFeedback();
@@ -234,7 +239,7 @@
 
     @Override
     public Intent getLaunchIntent() {
-        if (mInQuickSwitchMode || mSwipeUpOverHome) {
+        if (mInQuickSwitchMode || mSwipeUpOverHome || !mDeviceState.isFullyGesturalNavMode()) {
             return mGestureState.getOverviewIntent();
         } else {
             return mGestureState.getHomeIntent();
@@ -248,6 +253,11 @@
             mRecentsAnimationController.setWindowThresholdCrossed(!mInQuickSwitchMode
                     && (mCurrentShift.value > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD));
         }
+
+        if (!mInQuickSwitchMode && !mDeviceState.isFullyGesturalNavMode()) {
+            updateOverviewThresholdPassed(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW);
+        }
+
         if (mRecentsAnimationTargets != null) {
             applyTransformUnchecked();
         }
@@ -271,14 +281,25 @@
                     .getDimension(R.dimen.quickstep_fling_threshold_velocity);
             boolean isFling = Math.abs(endVelocity) > flingThreshold;
 
-            if (isFling) {
-                mGestureState.setEndTarget(endVelocity < 0 ? HOME : LAST_TASK);
-            } else if (mIsMotionPaused) {
-                mGestureState.setEndTarget(RECENTS);
+            if (mDeviceState.isFullyGesturalNavMode()) {
+                if (isFling) {
+                    mGestureState.setEndTarget(endVelocity < 0 ? HOME : LAST_TASK);
+                } else if (mOverviewThresholdPassed) {
+                    mGestureState.setEndTarget(RECENTS);
+                } else {
+                    mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
+                            ? HOME
+                            : LAST_TASK);
+                }
             } else {
-                mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
-                        ? HOME
-                        : LAST_TASK);
+                GestureEndTarget startState = mSwipeUpOverHome ? HOME : LAST_TASK;
+                if (isFling) {
+                    mGestureState.setEndTarget(endVelocity < 0 ? RECENTS : startState);
+                } else {
+                    mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
+                            ? RECENTS
+                            : startState);
+                }
             }
         }
         mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
@@ -353,7 +374,7 @@
                 mRecentsAnimationController.finish(false, null, false);
                 break;
             case RECENTS: {
-                if (mSwipeUpOverHome) {
+                if (mSwipeUpOverHome || !mDeviceState.isFullyGesturalNavMode()) {
                     mRecentsAnimationController.finish(true, null, true);
                     break;
                 }
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 f2e8f96..3ab0f19 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -70,7 +70,7 @@
         setContentView(R.layout.fallback_recents_activity);
         mRecentsRootView = findViewById(R.id.drag_layer);
         mFallbackRecentsView = findViewById(R.id.overview_panel);
-        mRecentsRootView.setup();
+        mRecentsRootView.recreateControllers();
     }
 
     @Override
@@ -108,7 +108,7 @@
     @Override
     protected void onHandleConfigChanged() {
         super.onHandleConfigChanged();
-        mRecentsRootView.setup();
+        mRecentsRootView.recreateControllers();
     }
 
     @Override
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 0f63336..25a3078 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -50,8 +50,6 @@
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.PagedView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.config.FeatureFlags;
@@ -351,17 +349,6 @@
                 OverscrollPlugin.class, false /* allowMultiple */);
     }
 
-    private void onDeferredActivityLaunch() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mOverviewComponentObserver.getActivityInterface().switchRunningTaskViewToScreenshot(
-                    null, () -> {
-                        mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
-                    });
-        } else {
-            mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
-        }
-    }
-
     private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
         if (!mDeviceState.isUserUnlocked() || mDeviceState.isButtonNavMode()) {
             // Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
@@ -466,8 +453,11 @@
                 // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should
                 // not interrupt it. QuickSwitch assumes that interruption can only happen if the
                 // next gesture is also quick switch.
-                mUncheckedConsumer = new AssistantInputConsumer(this, newGestureState,
-                        InputConsumer.NO_OP, mInputMonitorCompat);
+                mUncheckedConsumer = new AssistantInputConsumer(
+                    this,
+                    newGestureState,
+                    InputConsumer.NO_OP, mInputMonitorCompat,
+                    mOverviewComponentObserver.assistantGestureIsConstrained());
             } else {
                 mUncheckedConsumer = InputConsumer.NO_OP;
             }
@@ -502,10 +492,17 @@
                         ? newBaseConsumer(previousGestureState, newGestureState, event)
                         : mResetGestureInputConsumer;
         // TODO(b/149880412): 2 button landscape mode is wrecked. Fixit!
-        if (mDeviceState.isFullyGesturalNavMode()) {
+        if (mDeviceState.isGesturalNavMode()) {
             handleOrientationSetup(base);
+        }
+        if (mDeviceState.isFullyGesturalNavMode()) {
             if (mDeviceState.canTriggerAssistantAction(event)) {
-                base = new AssistantInputConsumer(this, newGestureState, base, mInputMonitorCompat);
+                base = new AssistantInputConsumer(
+                    this,
+                    newGestureState,
+                    base,
+                    mInputMonitorCompat,
+                    mOverviewComponentObserver.assistantGestureIsConstrained());
             }
 
             if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) {
@@ -548,19 +545,19 @@
     }
 
     private void handleOrientationSetup(InputConsumer baseInputConsumer) {
-        if (!PagedView.sFlagForcedRotation) {
+        if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
             return;
         }
         mDeviceState.enableMultipleRegions(baseInputConsumer instanceof OtherActivityInputConsumer);
-        Launcher l = (Launcher) mOverviewComponentObserver
-            .getActivityInterface().getCreatedActivity();
-        if (l == null || !(l.getOverviewPanel() instanceof RecentsView)) {
+        BaseDraggingActivity activity =
+                mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
+        if (activity == null || !(activity.getOverviewPanel() instanceof RecentsView)) {
             return;
         }
-        ((RecentsView)l.getOverviewPanel())
+        ((RecentsView) activity.getOverviewPanel())
             .setLayoutRotation(mDeviceState.getCurrentActiveRotation(),
                 mDeviceState.getDisplayRotation());
-        l.getDragLayer().recreateControllers();
+        activity.getDragLayer().recreateControllers();
     }
 
     private InputConsumer newBaseConsumer(GestureState previousGestureState,
@@ -616,11 +613,7 @@
 
         if (!mOverviewComponentObserver.isHomeAndOverviewSame()) {
             shouldDefer = previousGestureState.getFinishingRecentsAnimationTaskId() < 0;
-            if (mDeviceState.isFullyGesturalNavMode()) {
-                factory = mFallbackSwipeHandlerFactory;
-            } else {
-                factory = this::determineFallbackTwoButtonSwipeHandler;
-            }
+            factory = mFallbackSwipeHandlerFactory;
         } else {
             shouldDefer = gestureState.getActivityInterface().deferStartingActivity(mDeviceState,
                     event);
@@ -633,23 +626,6 @@
                 mInputMonitorCompat, disableHorizontalSwipe, factory);
     }
 
-    /**
-     * Determines whether to use the LauncherSwipeHandler or FallbackSwipeHandler at runtime.
-     * We need to use the FallbackSwipeHandler to handle quick switch from home, otherwise the
-     * normal LauncherSwipeHandler works.
-     */
-    private BaseSwipeUpHandler determineFallbackTwoButtonSwipeHandler(GestureState gestureState,
-            long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
-        boolean runningOverHome = gestureState.getRunningTask() == null
-                || ActivityManagerWrapper.isHomeTask(gestureState.getRunningTask());
-        boolean isQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
-        BaseSwipeUpHandler.Factory factory = runningOverHome && isQuickSwitchMode
-                ? mFallbackSwipeHandlerFactory
-                : mLauncherSwipeHandlerFactory;
-        return factory.newHandler(gestureState, touchTimeMs, continuingLastGesture,
-                isLikelyToStartNewTask);
-    }
-
     private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState) {
         if (mDeviceState.isFullyGesturalNavMode() && gestureState.getRunningTask() != null) {
             return new DeviceLockedInputConsumer(this, mDeviceState, mTaskAnimationManager,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
index de5fd7c..2c5d631 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
@@ -21,7 +21,6 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 
-import com.android.launcher3.BaseActivity;
 import com.android.launcher3.R;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TouchController;
@@ -31,13 +30,11 @@
 public class RecentsRootView extends BaseDragLayer<RecentsActivity> {
 
     private static final int MIN_SIZE = 10;
-    private final RecentsActivity mActivity;
 
     private final Point mLastKnownSize = new Point(MIN_SIZE, MIN_SIZE);
 
     public RecentsRootView(Context context, AttributeSet attrs) {
         super(context, attrs, 1 /* alphaChannelCount */);
-        mActivity = BaseActivity.fromContext(context);
         setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                 | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                 | SYSTEM_UI_FLAG_LAYOUT_STABLE);
@@ -47,7 +44,8 @@
         return mLastKnownSize;
     }
 
-    public void setup() {
+    @Override
+    public void recreateControllers() {
         mControllers = new TouchController[] {
                 new RecentsTaskController(mActivity),
                 new FallbackNavBarTouchController(mActivity),
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
index 94126ff..89e6931 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
@@ -90,12 +90,18 @@
     private final float mSquaredSlop;
     private final Context mContext;
     private final GestureDetector mGestureDetector;
+    private final boolean mIsAssistGestureConstrained;
 
-    public AssistantInputConsumer(Context context, GestureState gestureState,
-            InputConsumer delegate, InputMonitorCompat inputMonitor) {
+    public AssistantInputConsumer(
+            Context context,
+            GestureState gestureState,
+            InputConsumer delegate,
+            InputMonitorCompat inputMonitor,
+            boolean isAssistGestureConstrained) {
         super(delegate, inputMonitor);
         final Resources res = context.getResources();
         mContext = context;
+        mIsAssistGestureConstrained = isAssistGestureConstrained;
         mDragDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
         mFlingDistThreshold = res.getDimension(R.dimen.gestures_assistant_fling_threshold);
         mTimeThreshold = res.getInteger(R.integer.assistant_gesture_min_time_threshold);
@@ -258,7 +264,8 @@
     private class AssistantGestureListener extends SimpleOnGestureListener {
         @Override
         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-            if (isValidAssistantGestureAngle(velocityX, -velocityY)
+            if (!mIsAssistGestureConstrained
+                && isValidAssistantGestureAngle(velocityX, -velocityY)
                     && mDistance >= mFlingDistThreshold
                     && !mLaunchedAssistant
                     && mState != STATE_DELEGATE_ACTIVE) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index d2805ed..7e17fbf 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -18,7 +18,8 @@
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL_COMPONENTS;
+import static com.android.launcher3.LauncherStateManager.SKIP_OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 
 import android.animation.Animator;
@@ -32,7 +33,6 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
@@ -143,11 +143,10 @@
      * Setup workspace with 0 duration to prepare for our staggered animation.
      */
     private void prepareToAnimate(Launcher launcher) {
-        LauncherStateManager stateManager = launcher.getStateManager();
         AnimatorSetBuilder builder = new AnimatorSetBuilder();
         // setRecentsAttachedToAppWindow() will animate recents out.
-        builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
-        stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0);
+        launcher.getStateManager().createAtomicAnimation(
+                BACKGROUND_APP, NORMAL, builder, ANIM_ALL_COMPONENTS | SKIP_OVERVIEW, 0);
         builder.build().start();
 
         // Stop scrolling so that it doesn't interfere with the translation offscreen.
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 f60da18..3ed7530 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -42,13 +42,13 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.StateListener;
-import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.appprediction.PredictionUiStateManager;
 import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
 import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.views.ScrimView;
@@ -287,7 +287,8 @@
 
     @Override
     protected boolean supportsVerticalLandscape() {
-        return PagedView.sFlagForcedRotation;
+        return FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()
+                && !mOrientationState.areMultipleLayoutOrientationsDisabled();
     }
 
     @Override
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 f60eaa7..b5e6af4 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
@@ -37,7 +37,6 @@
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 
-import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
 import android.animation.LayoutTransition.TransitionListener;
@@ -170,7 +169,6 @@
                 }
             };
 
-    private final OrientationEventListener mOrientationListener;
     private int mPreviousRotation;
     protected RecentsAnimationController mRecentsAnimationController;
     protected RecentsAnimationTargets mRecentsAnimationTargets;
@@ -378,22 +376,6 @@
 
         // Initialize quickstep specific cache params here, as this is constructed only once
         mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
-        mOrientationListener = new OrientationEventListener(getContext()) {
-            @Override
-            public void onOrientationChanged(int i) {
-                int rotation = RotationHelper.getRotationFromDegrees(i);
-                if (mPreviousRotation != rotation) {
-                    animateRecentsRotationInPlace(rotation);
-                    if (rotation == 0) {
-                        showActionsView();
-                    } else {
-                        hideActionsView();
-                    }
-                    mPreviousRotation = rotation;
-                }
-            }
-        };
-
     }
 
     public OverScroller getScroller() {
@@ -522,13 +504,6 @@
     }
 
     public void setOverviewStateEnabled(boolean enabled) {
-        if (supportsVerticalLandscape() && mOrientationListener.canDetectOrientation()) {
-            if (enabled) {
-                mOrientationListener.enable();
-            } else {
-                mOrientationListener.disable();
-            }
-        }
         mOverviewStateEnabled = enabled;
         updateTaskStackListenerState();
         if (!enabled) {
@@ -700,6 +675,21 @@
         return taskViewCount;
     }
 
+    /**
+     * Updates UI for a modal task, including hiding other tasks.
+     */
+    public void updateUiForModalTask(TaskView taskView, boolean isTaskOverlayModal) {
+        int currentIndex = indexOfChild(taskView);
+        TaskView previousTask = getTaskViewAt(currentIndex - 1);
+        TaskView nextTask = getTaskViewAt(currentIndex + 1);
+        if (previousTask != null) {
+            previousTask.setVisibility(isTaskOverlayModal ? View.INVISIBLE : View.VISIBLE);
+        }
+        if (nextTask != null) {
+            nextTask.setVisibility(isTaskOverlayModal ? View.INVISIBLE : View.VISIBLE);
+        }
+    }
+
     protected void onTaskStackUpdated() { }
 
     public void resetTaskVisuals() {
@@ -954,35 +944,6 @@
         setSwipeDownShouldLaunchApp(true);
     }
 
-    private void animateRecentsRotationInPlace(int newRotation) {
-        if (!supportsVerticalLandscape()) {
-            return;
-        }
-
-        AnimatorSet pa = setRecentsChangedOrientation(true);
-        pa.addListener(AnimationSuccessListener.forRunnable(() -> {
-            updateLayoutRotation(newRotation);
-            ((DragLayer) mActivity.getDragLayer()).recreateControllers();
-            rotateAllChildTasks();
-            setRecentsChangedOrientation(false).start();
-        }));
-        pa.start();
-    }
-
-    public AnimatorSet setRecentsChangedOrientation(boolean fadeInChildren) {
-        getRunningTaskIndex();
-        int runningIndex = getCurrentPage();
-        AnimatorSet as = new AnimatorSet();
-        for (int i = 0; i < getTaskViewCount(); i++) {
-            if (runningIndex == i) {
-                continue;
-            }
-            View taskView = getTaskViewAt(i);
-            as.play(ObjectAnimator.ofFloat(taskView, View.ALPHA, fadeInChildren ? 0 : 1));
-        }
-        return as;
-    }
-
     abstract protected boolean supportsVerticalLandscape();
 
     private void rotateAllChildTasks() {
@@ -1506,7 +1467,7 @@
 
     @Override
     public void setLayoutRotation(int touchRotation, int displayRotation) {
-        if (!sFlagForcedRotation) {
+        if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
             return;
         }
 
@@ -1958,7 +1919,8 @@
     public Consumer<MotionEvent> getEventDispatcher(RotationMode navBarRotationMode) {
         float degreesRotated;
         if (navBarRotationMode == RotationMode.NORMAL) {
-            degreesRotated = RotationHelper.getDegreesFromRotation(mLayoutRotation);
+            degreesRotated = mOrientationState.areMultipleLayoutOrientationsDisabled() ? 0 :
+                    RotationHelper.getDegreesFromRotation(mLayoutRotation);
         } else {
             degreesRotated = -navBarRotationMode.surfaceRotation;
         }
@@ -1970,6 +1932,13 @@
         // undo that transformation since PagedView also accommodates for the transformation via
         // PagedOrientationHandler
         return e -> {
+            if (navBarRotationMode != RotationMode.NORMAL
+                    && !mOrientationState.areMultipleLayoutOrientationsDisabled()) {
+                RotationHelper.transformEventForNavBar(e, true);
+                super.onTouchEvent(e);
+                RotationHelper.transformEventForNavBar(e, false);
+                return;
+            }
             RotationHelper.transformEvent(-degreesRotated, e, true);
             super.onTouchEvent(e);
             RotationHelper.transformEvent(-degreesRotated, e, false);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index abc037e..0e1640e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -245,6 +245,13 @@
         return mSnapshotView.getTaskOverlay().isOverlayModal();
     }
 
+    /** Updates UI based on whether the task is modal. */
+    public void updateUiForModalTask() {
+        if (getRecentsView() != null) {
+            getRecentsView().updateUiForModalTask(this, isTaskOverlayModal());
+        }
+    }
+
     public TaskMenuView getMenuView() {
         return mMenuView;
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
index 43dc882..671aab0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
@@ -40,7 +40,7 @@
     @Override
     public void setStateWithAnimation(LauncherState toState,
             AnimatorSetBuilder builder, LauncherStateManager.AnimationConfig config) {
-        if (!config.playNonAtomicComponent()) {
+        if (config.onlyPlayAtomicComponent()) {
             return;
         }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java b/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java
index 9e4ada7..022a5f7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java
@@ -20,6 +20,7 @@
 
 import android.util.IntProperty;
 import android.view.View;
+
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
@@ -134,7 +135,7 @@
     @Override
     public void setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder,
             LauncherStateManager.AnimationConfig config) {
-        if (mSurface == null || !config.playNonAtomicComponent()) {
+        if (mSurface == null || config.onlyPlayAtomicComponent()) {
             return;
         }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 9b57fb7..94e67f0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -20,12 +20,14 @@
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_PEEK;
+import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_SCALE;
+import static com.android.launcher3.LauncherStateManager.SKIP_OVERVIEW;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCRIM_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
-import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
@@ -86,13 +88,11 @@
     @Override
     public final void setStateWithAnimation(@NonNull final LauncherState toState,
             @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
-        boolean playAtomicOverviewComponent = config.playAtomicOverviewScaleComponent()
-                || config.playAtomicOverviewPeekComponent();
-        if (!playAtomicOverviewComponent) {
+        if (!config.hasAnimationFlag(PLAY_ATOMIC_OVERVIEW_PEEK | PLAY_ATOMIC_OVERVIEW_SCALE)) {
             // The entire recents animation is played atomically.
             return;
         }
-        if (builder.hasFlag(FLAG_DONT_ANIMATE_OVERVIEW)) {
+        if (config.hasAnimationFlag(SKIP_OVERVIEW)) {
             return;
         }
         setStateWithAnimationInternal(toState, builder, config);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java b/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
new file mode 100644
index 0000000..548223a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Size;
+import android.view.View;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.graphics.LauncherPreviewRenderer;
+import com.android.systemui.shared.system.SurfaceViewRequestReceiver;
+
+/** Render preview using surface view. */
+public class PreviewSurfaceRenderer {
+
+    /** Handle a received surface view request. */
+    public static void render(Context context, Bundle bundle) {
+        final String gridName = bundle.getString("name");
+        bundle.remove("name");
+        final InvariantDeviceProfile idp = new InvariantDeviceProfile(context, gridName);
+
+        MAIN_EXECUTOR.execute(() -> {
+            View view = new LauncherPreviewRenderer(context, idp).getRenderedView();
+            new SurfaceViewRequestReceiver().onReceive(context, bundle, view,
+                    new Size(view.getMeasuredWidth(), view.getMeasuredHeight()));
+        });
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
index 3cb0088..3d0fc56 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
@@ -9,7 +9,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationComponents;
+import com.android.launcher3.LauncherStateManager.AnimationFlags;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -56,7 +56,7 @@
     }
 
     @Override
-    protected float initCurrentAnimation(@AnimationComponents int animComponent) {
+    protected float initCurrentAnimation(@AnimationFlags int animComponent) {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);
         mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 95e38e3..a060d64 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -37,7 +37,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationComponents;
+import com.android.launcher3.LauncherStateManager.AnimationFlags;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
@@ -207,7 +207,8 @@
     }
 
     @Override
-    protected float initCurrentAnimation(@AnimationComponents int animComponents) {
+    protected float initCurrentAnimation(@AnimationFlags int animComponents) {
+        animComponents = updateAnimComponentsOnReinit(animComponents);
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);
 
@@ -218,7 +219,6 @@
 
         final AnimatorSetBuilder builder = totalShift == 0 ? new AnimatorSetBuilder()
                 : getAnimatorSetBuilderForStates(mFromState, mToState);
-        updateAnimatorBuilderOnReinit(builder);
 
         cancelPendingAnim();
 
@@ -253,7 +253,9 @@
     /**
      * Give subclasses the chance to update the animation when we re-initialize towards a new state.
      */
-    protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) {
+    @AnimationFlags
+    protected int updateAnimComponentsOnReinit(@AnimationFlags int animComponents) {
+        return animComponents;
     }
 
     private void cancelPendingAnim() {
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 92eb036..3e73f49 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -75,8 +75,12 @@
         mContractInfo = contractInfo;
     }
 
-    void setNavigationMode(SysUINavigationMode.Mode newMode) {
+    void setNavigationMode(SysUINavigationMode.Mode newMode, DefaultDisplay.Info info) {
+        if (mMode == newMode) {
+            return;
+        }
         this.mMode = newMode;
+        resetSwipeRegions(info);
     }
 
     /**
@@ -120,7 +124,9 @@
             mQuickStepStartingRotation = -1;
             resetSwipeRegions(info);
         } else {
-            if (mQuickStepStartingRotation < 0) {
+            if (mLastRectTouched != null) {
+                // mLastRectTouched can be null if gesture type is changed (ex. from settings)
+                // but nav bar hasn't been interacted with yet.
                 mQuickStepStartingRotation = mLastRectTouched.mRotation;
             }
         }
@@ -138,10 +144,8 @@
         }
 
         mCurrentRotation = region.rotation;
-        OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentRotation);
         mSwipeTouchRegions.clear();
-        mSwipeTouchRegions.put(mCurrentRotation,
-                regionToKeep != null ? regionToKeep : createRegionForDisplay(region));
+        mSwipeTouchRegions.put(mCurrentRotation, createRegionForDisplay(region));
     }
 
     private OrientationRectF createRegionForDisplay(DefaultDisplay.Info display) {
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 866836e..9edc86e 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -22,6 +22,7 @@
 
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED;
 
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -104,6 +105,10 @@
         updateOverviewTargets();
     }
 
+    public boolean assistantGestureIsConstrained() {
+        return (mDeviceState.getSystemUiStateFlags() & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0;
+    }
+
     /**
      * Update overview intent and {@link BaseActivityInterface} based off the current launcher home
      * component.
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 85464aa..1299a53 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -47,9 +47,9 @@
 
 import androidx.annotation.BinderThread;
 
-import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.DefaultDisplay;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
@@ -100,13 +100,23 @@
         }
     };
 
+    private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
+        @Override
+        public void onRecentTaskListFrozenChanged(boolean frozen) {
+            if (frozen) {
+                return;
+            }
+            mOrientationTouchTransformer.enableMultipleRegions(false, mDefaultDisplay.getInfo());
+        }
+    };
+
     private OrientationTouchTransformer mOrientationTouchTransformer;
 
     private Region mExclusionRegion;
     private SystemGestureExclusionListenerCompat mExclusionListener;
 
     private final List<ComponentName> mGestureBlockedActivities;
-    private TaskStackChangeListener mFrozenTaskListener;
+    private Runnable mOnDestroyFrozenTaskRunnable;
 
     public RecentsAnimationDeviceState(Context context) {
         final ContentResolver resolver = context.getContentResolver();
@@ -136,7 +146,9 @@
         };
         runOnDestroy(mExclusionListener::unregister);
 
-        setupOrientationSwipeHandler(context);
+        Resources resources = mContext.getResources();
+        mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
+                () -> QuickStepContract.getWindowCornerRadius(resources));
 
         // Register for navigation mode changes
         onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
@@ -159,24 +171,20 @@
         }
     }
 
-    private void setupOrientationSwipeHandler(Context context) {
-        final Resources resources = context.getResources();
-        mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
-                () -> QuickStepContract.getWindowCornerRadius(resources));
-
-        if (!PagedView.sFlagForcedRotation) {
+    private void setupOrientationSwipeHandler() {
+        if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
             return;
         }
 
-        mFrozenTaskListener = new TaskStackChangeListener() {
-            @Override
-            public void onRecentTaskListFrozenChanged(boolean frozen) {
-                mOrientationTouchTransformer.enableMultipleRegions(frozen, mDefaultDisplay.getInfo());
-            }
-        };
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
-        runOnDestroy(() -> ActivityManagerWrapper.getInstance()
-                .unregisterTaskStackListener(mFrozenTaskListener));
+        mOnDestroyFrozenTaskRunnable = () -> ActivityManagerWrapper.getInstance()
+                .unregisterTaskStackListener(mFrozenTaskListener);
+        runOnDestroy(mOnDestroyFrozenTaskRunnable);
+    }
+
+    private void destroyOrientationSwipeHandlerCallback() {
+        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mFrozenTaskListener);
+        mOnDestroyActions.remove(mOnDestroyFrozenTaskRunnable);
     }
 
     private void runOnDestroy(Runnable action) {
@@ -216,11 +224,17 @@
         } else {
             mExclusionListener.unregister();
         }
+
+        mNavBarPosition = new NavBarPosition(newMode, mDefaultDisplay.getInfo());
+
+        mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo());
+        if (!mMode.hasGestures && newMode.hasGestures) {
+            setupOrientationSwipeHandler();
+        } else if (mMode.hasGestures && !newMode.hasGestures){
+            destroyOrientationSwipeHandlerCallback();
+        }
+
         mMode = newMode;
-
-        mNavBarPosition = new NavBarPosition(mMode, mDefaultDisplay.getInfo());
-
-        mOrientationTouchTransformer.setNavigationMode(mMode);
     }
 
     @Override
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 4ac815e..0afe4a8 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -166,12 +166,9 @@
             return false;
         }
 
-        setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY,
-                overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY);
-        setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY,
-                overlayPackage == NAV_BAR_MODE_2BUTTON_OVERLAY);
-        setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY,
-                overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
+        Log.d(TAG, "setActiveOverlay: " + overlayPackage + "...");
+        UiDevice.getInstance(getInstrumentation()).executeShellCommand(
+                "cmd overlay enable-exclusive " + overlayPackage);
 
         if (currentSysUiNavigationMode() != expectedMode) {
             final CountDownLatch latch = new CountDownLatch(1);
@@ -210,14 +207,6 @@
         return true;
     }
 
-    private static void setOverlayPackageEnabled(String overlayPackage, boolean enable)
-            throws Exception {
-        Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable);
-        final String action = enable ? "enable" : "disable";
-        UiDevice.getInstance(getInstrumentation()).executeShellCommand(
-                "cmd overlay " + action + " " + overlayPackage);
-    }
-
     private static boolean packageExists(String packageName) {
         try {
             PackageManager pm = getInstrumentation().getContext().getPackageManager();
diff --git a/res/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
index 1c18076..cf1e835 100644
--- a/res/layout/work_apps_paused.xml
+++ b/res/layout/work_apps_paused.xml
@@ -15,19 +15,10 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="?attr/allAppsScrimColor"
-    android:padding="8dp"
+    android:padding="48dp"
     android:orientation="vertical"
     android:gravity="center">
 
-    <ImageView
-        android:id="@+id/icon"
-        android:contentDescription="@string/work_apps_paused_title"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
-        android:tint="?attr/workProfileOverlayTextColor"
-        android:src="@drawable/ic_corp_off" />
-
     <TextView
         style="@style/TextHeadline"
         android:textColor="?attr/workProfileOverlayTextColor"
diff --git a/res/layout/work_profile_edu.xml b/res/layout/work_profile_edu.xml
index 04094c4..5506b94 100644
--- a/res/layout/work_profile_edu.xml
+++ b/res/layout/work_profile_edu.xml
@@ -41,6 +41,7 @@
             android:layout_height="wrap_content"
             android:layout_marginTop="48dp"
             android:layout_marginBottom="48dp"
+            android:gravity="center"
             android:text="@string/work_profile_edu_personal_apps"
             android:textAlignment="center"
             android:textColor="@android:color/white"
diff --git a/res/layout/work_tab_footer.xml b/res/layout/work_tab_footer.xml
index 2cffedd..dbcdbdb 100644
--- a/res/layout/work_tab_footer.xml
+++ b/res/layout/work_tab_footer.xml
@@ -19,19 +19,20 @@
     android:layout_height="wrap_content"
     android:id="@+id/work_toggle_container"
     android:focusable="true"
-    android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_padding_vertical"
     android:orientation="horizontal"
-    android:paddingLeft="@dimen/all_apps_work_profile_tab_footer_padding_horizontal"
     android:background="?attr/allAppsScrimColor"
-    android:paddingRight="@dimen/all_apps_work_profile_tab_footer_padding_horizontal"
-    android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding_vertical">
+    android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_padding"
+    android:paddingLeft="@dimen/all_apps_work_profile_tab_footer_padding"
+    android:paddingRight="@dimen/all_apps_work_profile_tab_footer_padding"
+    android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding">
 
     <TextView
+        style="@style/PrimaryMediumText"
         android:id="@+id/work_mode_label"
         android:layout_width="0dp"
         android:layout_weight="1"
         android:drawableStart="@drawable/ic_corp"
-        android:drawablePadding="3dp"
+        android:drawablePadding="16dp"
         android:drawableTint="?attr/workProfileOverlayTextColor"
         android:textColor="?attr/workProfileOverlayTextColor"
         android:layout_height="wrap_content"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index edae7f4..871651d 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -85,8 +85,7 @@
     <dimen name="all_apps_tabs_side_padding">12dp</dimen>
     <dimen name="all_apps_divider_height">1dp</dimen>
 
-    <dimen name="all_apps_work_profile_tab_footer_padding_vertical">20dp</dimen>
-    <dimen name="all_apps_work_profile_tab_footer_padding_horizontal">24dp</dimen>
+    <dimen name="all_apps_work_profile_tab_footer_padding">20dp</dimen>
 
 <!-- Search bar in All Apps -->
     <dimen name="all_apps_header_max_elevation">3dp</dimen>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index cee268b..bc6ab45 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -225,6 +225,7 @@
     <style name="DropTargetButton" parent="DropTargetButtonBase" />
 
     <style name="TextHeadline" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" />
+    <style name="PrimaryMediumText" parent="@android:style/TextAppearance.DeviceDefault.Medium"/>
 
     <style name="TextTitle" parent="@android:style/TextAppearance.DeviceDefault" />
 
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 864fa6e..38e1201 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -22,11 +22,11 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.recyclerview.widget.RecyclerView;
+
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
-import androidx.recyclerview.widget.RecyclerView;
-
 
 /**
  * A base {@link RecyclerView}, which does the following:
@@ -138,7 +138,7 @@
         if (getCurrentScrollY() == 0) {
             return true;
         }
-        return false;
+        return getAdapter() == null || getAdapter().getItemCount() == 0;
     }
 
     /**
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index bc0e75f..1413a5c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -329,6 +329,16 @@
     private BackgroundBlurController mBackgroundBlurController =
             new BackgroundBlurController(this);
 
+    private final ViewTreeObserver.OnDrawListener mOnDrawListener =
+            new ViewTreeObserver.OnDrawListener() {
+                @Override
+                public void onDraw() {
+                    getBackgroundBlurController().setSurfaceToLauncher(mDragLayer);
+                    mDragLayer.post(() -> mDragLayer.getViewTreeObserver().removeOnDrawListener(
+                            this));
+                }
+            };
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         Object traceToken = TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT,
@@ -930,6 +940,8 @@
         final int origDragLayerChildCount = mDragLayer.getChildCount();
         super.onStop();
 
+        mDragLayer.getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
+
         if (mDeferOverlayCallbacks) {
             checkIfOverlayStillDeferred();
         } else {
@@ -970,13 +982,7 @@
         if (!mDeferOverlayCallbacks) {
             mOverlayManager.onActivityStarted(this);
         }
-        mDragLayer.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
-            @Override
-            public void onDraw() {
-                getBackgroundBlurController().setSurfaceToLauncher(mDragLayer);
-                mDragLayer.post(() -> mDragLayer.getViewTreeObserver().removeOnDrawListener(this));
-            }
-        });
+        mDragLayer.getViewTreeObserver().addOnDrawListener(mOnDrawListener);
 
         mAppWidgetHost.setListenIfResumed(true);
         TraceHelper.INSTANCE.endSection(traceToken);
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 4267f2b..04134f2 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -88,20 +88,22 @@
     // components may be run atomically - that is, all at once, instead of user-controlled. However,
     // atomic components are not restricted to this purpose; they can be user-controlled alongside
     // non atomic components as well. Note that each gesture model has exactly one atomic component,
-    // ATOMIC_OVERVIEW_SCALE_COMPONENT *or* ATOMIC_OVERVIEW_PEEK_COMPONENT.
+    // PLAY_ATOMIC_OVERVIEW_SCALE *or* PLAY_ATOMIC_OVERVIEW_PEEK.
     @IntDef(flag = true, value = {
-            NON_ATOMIC_COMPONENT,
-            ATOMIC_OVERVIEW_SCALE_COMPONENT,
-            ATOMIC_OVERVIEW_PEEK_COMPONENT,
+            PLAY_NON_ATOMIC,
+            PLAY_ATOMIC_OVERVIEW_SCALE,
+            PLAY_ATOMIC_OVERVIEW_PEEK,
+            SKIP_OVERVIEW,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface AnimationComponents {}
-    public static final int NON_ATOMIC_COMPONENT = 1 << 0;
-    public static final int ATOMIC_OVERVIEW_SCALE_COMPONENT = 1 << 1;
-    public static final int ATOMIC_OVERVIEW_PEEK_COMPONENT = 1 << 2;
+    public @interface AnimationFlags {}
+    public static final int PLAY_NON_ATOMIC = 1 << 0;
+    public static final int PLAY_ATOMIC_OVERVIEW_SCALE = 1 << 1;
+    public static final int PLAY_ATOMIC_OVERVIEW_PEEK = 1 << 2;
+    public static final int SKIP_OVERVIEW = 1 << 3;
 
-    public static final int ANIM_ALL = NON_ATOMIC_COMPONENT | ATOMIC_OVERVIEW_SCALE_COMPONENT
-            | ATOMIC_OVERVIEW_PEEK_COMPONENT;
+    public static final int ANIM_ALL_COMPONENTS = PLAY_NON_ATOMIC | PLAY_ATOMIC_OVERVIEW_SCALE
+            | PLAY_ATOMIC_OVERVIEW_PEEK;
 
     private final AnimationConfig mConfig = new AnimationConfig();
     private final Handler mUiHandler;
@@ -311,10 +313,10 @@
     }
 
     public AnimatorSet createAtomicAnimation(LauncherState fromState, LauncherState toState,
-            AnimatorSetBuilder builder, @AnimationComponents int atomicComponent, long duration) {
+            AnimatorSetBuilder builder, @AnimationFlags int animFlags, long duration) {
         prepareForAtomicAnimation(fromState, toState, builder);
         AnimationConfig config = new AnimationConfig();
-        config.animComponents = atomicComponent;
+        config.mAnimFlags = animFlags;
         config.duration = duration;
         for (StateHandler handler : mLauncher.getStateManager().getStateHandlers()) {
             handler.setStateWithAnimation(toState, builder, config);
@@ -355,21 +357,21 @@
      */
     public AnimatorPlaybackController createAnimationToNewWorkspace(
             LauncherState state, long duration) {
-        return createAnimationToNewWorkspace(state, duration, LauncherStateManager.ANIM_ALL);
+        return createAnimationToNewWorkspace(state, duration, ANIM_ALL_COMPONENTS);
     }
 
     public AnimatorPlaybackController createAnimationToNewWorkspace(
-            LauncherState state, long duration, @AnimationComponents int animComponents) {
+            LauncherState state, long duration, @AnimationFlags int animComponents) {
         return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration, null,
                 animComponents);
     }
 
     public AnimatorPlaybackController createAnimationToNewWorkspace(LauncherState state,
             AnimatorSetBuilder builder, long duration, Runnable onCancelRunnable,
-            @AnimationComponents int animComponents) {
+            @AnimationFlags int animComponents) {
         mConfig.reset();
         mConfig.userControlled = true;
-        mConfig.animComponents = animComponents;
+        mConfig.mAnimFlags = animComponents;
         mConfig.duration = duration;
         mConfig.playbackController = AnimatorPlaybackController.wrap(
                 createAnimationToNewWorkspaceInternal(state, builder, null), duration)
@@ -583,7 +585,7 @@
         public long duration;
         public boolean userControlled;
         public AnimatorPlaybackController playbackController;
-        public @AnimationComponents int animComponents = ANIM_ALL;
+        private @AnimationFlags int mAnimFlags = ANIM_ALL_COMPONENTS;
         private PropertySetter mPropertySetter;
 
         private AnimatorSet mCurrentAnimation;
@@ -597,7 +599,7 @@
         public void reset() {
             duration = 0;
             userControlled = false;
-            animComponents = ANIM_ALL;
+            mAnimFlags = ANIM_ALL_COMPONENTS;
             mPropertySetter = null;
             mTargetState = null;
 
@@ -638,16 +640,39 @@
             mCurrentAnimation.addListener(this);
         }
 
+        /**
+         * @return Whether Overview is scaling as part of this animation. If this is the only
+         * component (i.e. NON_ATOMIC_COMPONENT isn't included), then this scaling is happening
+         * atomically, rather than being part of a normal state animation. StateHandlers can use
+         * this to designate part of their animation that should scale with Overview.
+         */
         public boolean playAtomicOverviewScaleComponent() {
-            return (animComponents & ATOMIC_OVERVIEW_SCALE_COMPONENT) != 0;
+            return hasAnimationFlag(PLAY_ATOMIC_OVERVIEW_SCALE);
         }
 
-        public boolean playAtomicOverviewPeekComponent() {
-            return (animComponents & ATOMIC_OVERVIEW_PEEK_COMPONENT) != 0;
+        /**
+         * @return Whether this animation will play atomically at the same time as a different,
+         * user-controlled state transition. StateHandlers, which contribute to both animations, can
+         * use this to avoid animating the same properties in both animations, since they'd conflict
+         * with one another.
+         */
+        public boolean onlyPlayAtomicComponent() {
+            return getAnimComponents() == PLAY_ATOMIC_OVERVIEW_SCALE
+                    || getAnimComponents() == PLAY_ATOMIC_OVERVIEW_PEEK;
         }
 
-        public boolean playNonAtomicComponent() {
-            return (animComponents & NON_ATOMIC_COMPONENT) != 0;
+        /**
+         * Returns true if the config and any of the provided component flags
+         */
+        public boolean hasAnimationFlag(@AnimationFlags int a) {
+            return (mAnimFlags & a) != 0;
+        }
+
+        /**
+         * @return Only the flags that determine which animation components to play.
+         */
+        public @AnimationFlags int getAnimComponents() {
+            return mAnimFlags & ANIM_ALL_COMPONENTS;
         }
     }
 
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index a6180a6..e38631d 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -42,21 +42,6 @@
 import android.view.animation.Interpolator;
 import android.widget.ScrollView;
 
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.PagedViewOrientedState;
-import com.android.launcher3.pageindicators.PageIndicator;
-import com.android.launcher3.states.RotationHelper;
-import com.android.launcher3.touch.PortraitPagedViewHandler;
-import com.android.launcher3.touch.OverScroll;
-import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds;
-import com.android.launcher3.util.OverScroller;
-import com.android.launcher3.util.Thunk;
-
-import java.util.ArrayList;
-
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
@@ -65,6 +50,20 @@
 import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_BY;
 import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO;
 
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.PagedViewOrientedState;
+import com.android.launcher3.pageindicators.PageIndicator;
+import com.android.launcher3.touch.OverScroll;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds;
+import com.android.launcher3.touch.PortraitPagedViewHandler;
+import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.Thunk;
+
+import java.util.ArrayList;
+
 /**
  * An abstraction of the original Workspace which supports browsing through a
  * sequential list of "pages"
@@ -73,8 +72,6 @@
     private static final String TAG = "PagedView";
     private static final boolean DEBUG = false;
 
-    public static boolean sFlagForcedRotation = false;
-
     public static final int INVALID_PAGE = -1;
     protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
 
@@ -200,8 +197,6 @@
         if (Utilities.ATLEAST_OREO) {
             setDefaultFocusHighlightEnabled(false);
         }
-
-        sFlagForcedRotation = Utilities.isForcedRotation(context);
     }
 
     protected void setDefaultInterpolator(Interpolator interpolator) {
@@ -1515,7 +1510,7 @@
         // interpolator at zero, ie. 5. We use 4 to make it a little slower.
         duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
 
-        if (QUICKSTEP_SPRINGS.get()) {
+        if (QUICKSTEP_SPRINGS.get() && mCurrentPage != whichPage) {
             return snapToPage(whichPage, delta, duration, false, null,
                     velocity * Math.signum(delta), true);
         } else {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 9780630..122b393 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -17,7 +17,6 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
-import static com.android.launcher3.states.RotationHelper.FIXED_ROTATION_TRANSFORM_SETTING_NAME;
 
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
@@ -61,6 +60,7 @@
 import android.view.ViewConfiguration;
 import android.view.animation.Interpolator;
 
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.graphics.TintedDrawableSpan;
@@ -128,11 +128,6 @@
                         Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
     }
 
-    public static boolean isForcedRotation(Context context) {
-        return Settings.Global.getInt(context.getContentResolver(),
-            FIXED_ROTATION_TRANSFORM_SETTING_NAME, 0) != 0;
-    }
-
     // An intent extra to indicate the horizontal scroll of the wallpaper.
     public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
     public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR";
@@ -481,6 +476,15 @@
                 LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE);
     }
 
+    /**
+     * @return {@link SharedPreferences} that backs {@link FeatureFlags}
+     */
+    public static SharedPreferences getFeatureFlagsPrefs(Context context) {
+        // Use application context for shared preferences, so that we use a single cached instance
+        return context.getApplicationContext().getSharedPreferences(
+            FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE);
+    }
+
     public static boolean areAnimationsEnabled(Context context) {
         return ATLEAST_OREO
                 ? ValueAnimator.areAnimatorsEnabled()
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 388d074..6653426 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -120,7 +120,7 @@
                     hotseatIconsAlpha, fadeInterpolator);
         }
 
-        if (!config.playNonAtomicComponent()) {
+        if (config.onlyPlayAtomicComponent()) {
             // Only the alpha and scale, handled above, are included in the atomic animation.
             return;
         }
@@ -175,7 +175,8 @@
         float pageAlpha = pageAlphaProvider.getPageAlpha(childIndex);
         int drawableAlpha = Math.round(pageAlpha * (state.hasWorkspacePageBackground ? 255 : 0));
 
-        if (config.playNonAtomicComponent()) {
+        if (!config.onlyPlayAtomicComponent()) {
+            // Don't update the scrim during the atomic animation.
             propertySetter.setInt(cl.getScrimBackground(),
                     DRAWABLE_ALPHA, drawableAlpha, ZOOM_OUT);
         }
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index a2957bc..e085ff0 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,9 +15,6 @@
  */
 package com.android.launcher3.allapps;
 
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
-
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 
 import android.animation.ValueAnimator;
@@ -395,7 +392,7 @@
         rebindAdapters(showTabs, false /* force */);
     }
 
-    private void rebindAdapters(boolean showTabs, boolean force) {
+    protected void rebindAdapters(boolean showTabs, boolean force) {
         if (showTabs == mUsingTabs && !force) {
             return;
         }
@@ -463,6 +460,7 @@
     public void onTabChanged(int pos) {
         mHeader.setMainActive(pos == 0);
         reset(true /* animate */);
+        mViewPager.getPageIndicator().updateTabTextColor(pos);
         if (mAH[pos].recyclerView != null) {
             mAH[pos].recyclerView.bindFastScrollbar();
 
@@ -608,6 +606,7 @@
         public static final int MAIN = 0;
         public static final int WORK = 1;
 
+        private ItemInfoMatcher mInfoMatcher;
         private final boolean mIsWork;
         public final AllAppsGridAdapter adapter;
         final LinearLayoutManager layoutManager;
@@ -627,6 +626,7 @@
         }
 
         void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) {
+            mInfoMatcher = matcher;
             appsList.updateItemFilter(matcher);
             recyclerView = (AllAppsRecyclerView) rv;
             recyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
@@ -647,19 +647,14 @@
         void setupOverlay() {
             if (!mIsWork || recyclerView == null) return;
             boolean workDisabled = UserCache.INSTANCE.get(mLauncher).isAnyProfileQuietModeEnabled();
-            recyclerView.getOverlay().clear();
+            if (mWorkDisabled == workDisabled) return;
             if (workDisabled) {
-                View pausedOverlay = mLauncher.getLayoutInflater().inflate(
-                        R.layout.work_apps_paused, null);
-                recyclerView.post(() -> {
-                    int width = recyclerView.getWidth();
-                    int height = recyclerView.getHeight() -  mWorkFooterContainer.getHeight();
-                    pausedOverlay.measure(makeMeasureSpec(recyclerView.getWidth(), EXACTLY),
-                            makeMeasureSpec(recyclerView.getHeight(), EXACTLY));
-                    pausedOverlay.layout(0, 0, width, height);
-                    applyPadding();
-                });
-                recyclerView.getOverlay().add(pausedOverlay);
+                appsList.updateItemFilter((info, cn) -> false);
+                recyclerView.addAutoSizedOverlay(
+                        mLauncher.getLayoutInflater().inflate(R.layout.work_apps_paused, null));
+            } else if (mInfoMatcher != null) {
+                appsList.updateItemFilter(mInfoMatcher);
+                recyclerView.clearAutoSizedOverlays();
             }
             mWorkDisabled = workDisabled;
         }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index c228ddf..8fe4633 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,7 +15,9 @@
  */
 package com.android.launcher3.allapps;
 
+import static android.view.View.MeasureSpec.EXACTLY;
 import static android.view.View.MeasureSpec.UNSPECIFIED;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
 
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 
@@ -62,6 +64,8 @@
     private AllAppsBackgroundDrawable mEmptySearchBackground;
     private int mEmptySearchBackgroundTopOffset;
 
+    private ArrayList<View> mAutoSizedOverlays = new ArrayList<>();
+
     public AllAppsRecyclerView(Context context) {
         this(context, null);
     }
@@ -145,6 +149,30 @@
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         updateEmptySearchBackgroundBounds();
         updatePoolSize();
+        for (int i = 0; i < mAutoSizedOverlays.size(); i++) {
+            View overlay = mAutoSizedOverlays.get(i);
+            overlay.measure(makeMeasureSpec(w, EXACTLY), makeMeasureSpec(w, EXACTLY));
+            overlay.layout(0, 0, w, h);
+        }
+    }
+
+    /**
+     * Adds an overlay that automatically rescales with the recyclerview.
+     */
+    public void addAutoSizedOverlay(View overlay) {
+        mAutoSizedOverlays.add(overlay);
+        getOverlay().add(overlay);
+        onSizeChanged(getWidth(), getHeight(), getWidth(), getHeight());
+    }
+
+    /**
+     * Clears auto scaling overlay views added by #addAutoSizedOverlay
+     */
+    public void clearAutoSizedOverlays() {
+        for (View v : mAutoSizedOverlays) {
+            getOverlay().remove(v);
+        }
+        mAutoSizedOverlays.clear();
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 0e60f5b..744f4eb 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -162,7 +162,7 @@
             return;
         }
 
-        if (!config.playNonAtomicComponent()) {
+        if (config.onlyPlayAtomicComponent()) {
             // There is no atomic component for the all apps transition, so just return early.
             return;
         }
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index 0e39bbe..3e40392 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -71,12 +71,10 @@
         mIsRtl = Utilities.isRtl(getResources());
     }
 
-    private void updateIndicatorPosition(float scrollOffset) {
-        mScrollOffset = scrollOffset;
-        updateIndicatorPosition();
-    }
-
-    private void updateTabTextColor(int pos) {
+    /**
+     * Highlights tab with index pos
+     */
+    public void updateTabTextColor(int pos) {
         mSelectedPosition = pos;
         for (int i = 0; i < getChildCount(); i++) {
             Button tab = (Button) getChildAt(i);
@@ -84,6 +82,11 @@
         }
     }
 
+    private void updateIndicatorPosition(float scrollOffset) {
+        mScrollOffset = scrollOffset;
+        updateIndicatorPosition();
+    }
+
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index cd30dea..d814b19 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -21,7 +21,6 @@
 import android.view.animation.Interpolator;
 
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Utility class for building animator set
@@ -42,36 +41,17 @@
     public static final int ANIM_OVERVIEW_SCRIM_FADE = 11;
     public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions
 
-    public static final int FLAG_DONT_ANIMATE_OVERVIEW = 1 << 0;
-
     protected final ArrayList<Animator> mAnims = new ArrayList<>();
 
     private final SparseArray<Interpolator> mInterpolators = new SparseArray<>();
-    private List<Runnable> mOnFinishRunnables = new ArrayList<>();
-    private int mFlags = 0;
 
     public void play(Animator anim) {
         mAnims.add(anim);
     }
 
-    public void addOnFinishRunnable(Runnable onFinishRunnable) {
-        mOnFinishRunnables.add(onFinishRunnable);
-    }
-
     public AnimatorSet build() {
         AnimatorSet anim = new AnimatorSet();
         anim.playTogether(mAnims);
-        if (!mOnFinishRunnables.isEmpty()) {
-            anim.addListener(new AnimationSuccessListener() {
-                @Override
-                public void onAnimationSuccess(Animator animation) {
-                    for (Runnable onFinishRunnable : mOnFinishRunnables) {
-                        onFinishRunnable.run();
-                    }
-                    mOnFinishRunnables.clear();
-                }
-            });
-        }
         return anim;
     }
 
@@ -82,12 +62,4 @@
     public void setInterpolator(int animId, Interpolator interpolator) {
         mInterpolators.put(animId, interpolator);
     }
-
-    public void addFlag(int flag) {
-        mFlags |= flag;
-    }
-
-    public boolean hasFlag(int flag) {
-        return (mFlags & flag) != 0;
-    }
 }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index a6f9e6b..8453c41 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -36,6 +36,8 @@
     private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
 
     public static final String FLAGS_PREF_NAME = "featureFlags";
+    public static final String FLAG_ENABLE_FIXED_ROTATION_TRANSFORM =
+            "ENABLE_FIXED_ROTATION_TRANSFORM";
 
     private FeatureFlags() { }
 
@@ -98,11 +100,11 @@
             "Suggests folder names instead of blank text.");
 
     public static final BooleanFlag APP_SEARCH_IMPROVEMENTS = new DeviceFlag(
-            "APP_SEARCH_IMPROVEMENTS", false,
+            "APP_SEARCH_IMPROVEMENTS", true,
             "Adds localized title and keyword search and ranking");
 
-    public static final BooleanFlag ENABLE_PREDICTION_DISMISS = new DeviceFlag(
-            "ENABLE_PREDICTION_DISMISS", true, "Allow option to dimiss apps from predicted list");
+    public static final BooleanFlag ENABLE_PREDICTION_DISMISS = getDebugFlag(
+            "ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list");
 
     public static final BooleanFlag ENABLE_QUICK_CAPTURE_GESTURE = getDebugFlag(
             "ENABLE_QUICK_CAPTURE_GESTURE", true, "Swipe from right to left to quick capture");
@@ -115,11 +117,11 @@
             "ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
             "Allow Launcher to handle nav bar gestures while Assistant is running over it");
 
-    public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = new DeviceFlag(
+    public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = getDebugFlag(
             "ENABLE_HYBRID_HOTSEAT", false, "Fill gaps in hotseat with predicted apps");
 
-    public static final BooleanFlag HOTSEAT_MIGRATE_NEW_PAGE = new DeviceFlag(
-            "HOTSEAT_MIGRATE_NEW_PAGE", true,
+    public static final BooleanFlag HOTSEAT_MIGRATE_NEW_PAGE = getDebugFlag(
+            "HOTSEAT_MIGRATE_NEW_PAGE", false,
             "Migrates hotseat to a new workspace page instead of same page");
 
     public static final BooleanFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = getDebugFlag(
@@ -129,13 +131,18 @@
             "MULTI_DB_GRID_MIRATION_ALGO", false, "Use the multi-db grid migration algorithm");
 
     public static final BooleanFlag ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER = getDebugFlag(
-            "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", true,
-            "Show launcher preview in grid picker");
+            "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", true, "Show launcher preview in grid picker");
+
+    public static final BooleanFlag USE_SURFACE_VIEW_FOR_GRID_PREVIEW = getDebugFlag(
+            "USE_SURFACE_VIEW_FOR_GRID_PREVIEW", false, "Use surface view for grid preview");
 
     public static final BooleanFlag ENABLE_OVERVIEW_ACTIONS = getDebugFlag(
             "ENABLE_OVERVIEW_ACTIONS", true, "Show app actions instead of the shelf in Overview."
             + " As part of this decoupling, also distinguish swipe up from nav bar vs above it.");
 
+    public static final BooleanFlag ENABLE_SELECT_MODE = getDebugFlag(
+            "ENABLE_SELECT_MODE", true, "Show Select Mode button in Overview Actions");
+
     public static final BooleanFlag ENABLE_DATABASE_RESTORE = getDebugFlag(
             "ENABLE_DATABASE_RESTORE", true,
             "Enable database restore when new restore session is created");
@@ -153,6 +160,10 @@
             "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false,
             "Always use hardware optimization for folder animations.");
 
+    public static final BooleanFlag ENABLE_FIXED_ROTATION_TRANSFORM = getDebugFlag(
+            FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true,
+            "Launch/close apps without rotation animation. Fix Launcher to portrait");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 92f35e2..369bf28 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -119,6 +119,7 @@
         recreateControllers();
     }
 
+    @Override
     public void recreateControllers() {
         mControllers = mActivity.createTouchControllers();
     }
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 184dbb9..07161da 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -15,8 +15,10 @@
  */
 package com.android.launcher3.folder;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.os.Process;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -34,12 +36,9 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Function;
-import java.util.function.Predicate;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -101,27 +100,23 @@
         }
         // If all the icons are from work profile,
         // Then, suggest "Work" as the folder name
-        List<WorkspaceItemInfo> distinctItemInfos = workspaceItemInfos.stream()
-                .filter(distinctByKey(p -> p.user))
-                .collect(Collectors.toList());
-
-        if (distinctItemInfos.size() == 1
-                && !distinctItemInfos.get(0).user.equals(Process.myUserHandle())) {
-            // Place it as last viable suggestion
+        Set<UserHandle> users = workspaceItemInfos.stream().map(w -> w.user)
+                .collect(Collectors.toSet());
+        if (users.size() == 1 && !users.contains(Process.myUserHandle())) {
             setAsLastSuggestion(nameInfos,
                     context.getResources().getString(R.string.work_folder_name));
         }
 
         // If all the icons are from same package (e.g., main icon, shortcut, shortcut)
         // Then, suggest the package's title as the folder name
-        distinctItemInfos = workspaceItemInfos.stream()
-                .filter(distinctByKey(p -> p.getTargetComponent() != null
-                        ? p.getTargetComponent().getPackageName() : ""))
-                .collect(Collectors.toList());
+        Set<String> packageNames = workspaceItemInfos.stream()
+                .map(WorkspaceItemInfo::getTargetComponent)
+                .filter(Objects::nonNull)
+                .map(ComponentName::getPackageName)
+                .collect(Collectors.toSet());
 
-        if (distinctItemInfos.size() == 1) {
-            Optional<AppInfo> info = getAppInfoByPackageName(
-                    distinctItemInfos.get(0).getTargetComponent().getPackageName());
+        if (packageNames.size() == 1) {
+            Optional<AppInfo> info = getAppInfoByPackageName(packageNames.iterator().next());
             // Place it as first viable suggestion and shift everything else
             info.ifPresent(i -> setAsFirstSuggestion(nameInfos, i.title.toString()));
         }
@@ -135,6 +130,7 @@
             return Optional.empty();
         }
         return mAppInfos.stream()
+                .filter(info -> info.componentName != null)
                 .filter(info -> info.componentName.getPackageName().equals(packageName))
                 .findAny();
     }
@@ -174,12 +170,6 @@
                         label.toString()));
     }
 
-    // This method can be moved to some Utility class location.
-    private static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
-        Map<Object, Boolean> map = new ConcurrentHashMap<>();
-        return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
-    }
-
     private class FolderNameWorker extends BaseModelUpdateTask {
         @Override
         public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
diff --git a/src/com/android/launcher3/graphics/GridOptionsProvider.java b/src/com/android/launcher3/graphics/GridOptionsProvider.java
index 71b4366..607aba9 100644
--- a/src/com/android/launcher3/graphics/GridOptionsProvider.java
+++ b/src/com/android/launcher3/graphics/GridOptionsProvider.java
@@ -1,5 +1,6 @@
 package com.android.launcher3.graphics;
 
+import static com.android.launcher3.config.FeatureFlags.USE_SURFACE_VIEW_FOR_GRID_PREVIEW;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
 import android.content.ContentProvider;
@@ -19,6 +20,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.GridOption;
 import com.android.launcher3.R;
+import com.android.launcher3.uioverrides.PreviewSurfaceRenderer;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -62,6 +64,11 @@
     private static final String KEY_PREVIEW = "preview";
     private static final String MIME_TYPE_PNG = "image/png";
 
+    private static final String METHOD_GET_PREVIEW = "get_preview";
+    private static final String METADATA_KEY_PREVIEW_VERSION = "preview_version";
+
+
+
     public static final PipeDataWriter<Future<Bitmap>> BITMAP_WRITER =
             new PipeDataWriter<Future<Bitmap>>() {
                 @Override
@@ -98,6 +105,10 @@
                     .add(KEY_IS_DEFAULT, idp.numColumns == gridOption.numColumns
                             && idp.numRows == gridOption.numRows);
         }
+        Bundle metadata = new Bundle();
+        metadata.putString(METADATA_KEY_PREVIEW_VERSION,
+                USE_SURFACE_VIEW_FOR_GRID_PREVIEW.get() ? "V2" : "V1");
+        cursor.setExtras(metadata);
         return cursor;
     }
 
@@ -188,4 +199,14 @@
             throw new FileNotFoundException(e.getMessage());
         }
     }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras)  {
+        if (!METHOD_GET_PREVIEW.equals(method)) {
+            return null;
+        }
+
+        PreviewSurfaceRenderer.render(getContext(), extras);
+        return null;
+    }
 }
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 02e98e8..5bc6610 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -262,6 +262,13 @@
         });
     }
 
+    /** Populate preview and render it. */
+    public View getRenderedView() {
+        MainThreadRenderer renderer = new MainThreadRenderer(mContext);
+        renderer.populate();
+        return renderer.mRootView;
+    }
+
     private class MainThreadRenderer extends ContextThemeWrapper
             implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 {
 
@@ -388,7 +395,7 @@
             }
         }
 
-        private void renderScreenShot(Canvas canvas) {
+        private void populate() {
             if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
                 boolean needsToMigrate = needsToMigrate(mContext, mIdp);
                 boolean success = false;
@@ -499,7 +506,10 @@
             measureView(mRootView, mDp.widthPx, mDp.heightPx);
             // Additional measure for views which use auto text size API
             measureView(mRootView, mDp.widthPx, mDp.heightPx);
+        }
 
+        private void renderScreenShot(Canvas canvas) {
+            populate();
             mRootView.draw(canvas);
             dispatchVisibilityAggregated(mRootView, false);
         }
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 513bf62..3770d17 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -25,6 +25,9 @@
 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
 import static com.android.launcher3.logging.LoggerUtils.newTarget;
 import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.TipType;
 
 import static java.util.Optional.ofNullable;
 
@@ -48,7 +51,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -57,8 +60,11 @@
 import com.android.launcher3.util.LogConfig;
 import com.android.launcher3.util.ResourceBasedOverride;
 
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+import com.google.protobuf.nano.MessageNano;
+
 import java.util.ArrayList;
-import java.util.Locale;
 import java.util.UUID;
 
 /**
@@ -162,7 +168,7 @@
             // Direction DOWN means the task was launched, UP means it was dismissed.
             event.action.dir = direction;
         }
-        event.srcTarget[0].itemType = LauncherLogProto.ItemType.TASK;
+        event.srcTarget[0].itemType = ItemType.TASK;
         event.srcTarget[0].pageIndex = taskIndex;
         fillComponentInfo(event.srcTarget[0], componentKey.componentName);
         dispatchUserEvent(event, null);
@@ -289,7 +295,7 @@
     public void logActionBounceTip(int containerType) {
         LauncherEvent event = newLauncherEvent(newAction(Action.Type.TIP),
                 newContainerTarget(containerType));
-        event.srcTarget[0].tipType = LauncherLogProto.TipType.BOUNCE;
+        event.srcTarget[0].tipType = TipType.BOUNCE;
         dispatchUserEvent(event, null);
     }
 
@@ -316,7 +322,7 @@
             int srcChildTargetType, int srcParentContainerType, int dstContainerType,
             int pageIndex) {
         LauncherEvent event;
-        if (srcChildTargetType == LauncherLogProto.ItemType.TASK) {
+        if (srcChildTargetType == ItemType.TASK) {
             event = newLauncherEvent(newTouchAction(action),
                     newItemTarget(srcChildTargetType),
                     newContainerTarget(srcParentContainerType));
@@ -374,10 +380,11 @@
                 .setElapsedContainerMillis(SystemClock.uptimeMillis() - mElapsedContainerMillis)
                 .setElapsedSessionMillis(
                         SystemClock.uptimeMillis() - mElapsedSessionMillis).build();
-        if (!IS_VERBOSE) {
-            return;
+        try {
+            dispatchUserEvent(LauncherEvent.parseFrom(launcherEvent.toByteArray()), null);
+        } catch (InvalidProtocolBufferNanoException e) {
+            throw new RuntimeException("Cannot convert LauncherEvent from Lite to Nano version.");
         }
-        Log.d(TAG, launcherEvent.toString());
     }
 
     public void logDeepShortcutsOpen(View icon) {
@@ -421,8 +428,8 @@
         action.command = Action.Command.BACK;
         action.dir = isButton ? Action.Direction.NONE :
                 gestureSwipeLeft ? Action.Direction.LEFT : Action.Direction.RIGHT;
-        Target target = newControlTarget(isButton ? LauncherLogProto.ControlType.BACK_BUTTON :
-                LauncherLogProto.ControlType.BACK_GESTURE);
+        Target target = newControlTarget(isButton ? ControlType.BACK_BUTTON :
+                ControlType.BACK_GESTURE);
         target.spanX = downX;
         target.spanY = downY;
         target.cardinality = completed ? 1 : 0;
@@ -471,36 +478,14 @@
         if (!IS_VERBOSE) {
             return;
         }
-        Log.d(TAG, generateLog(ev));
-    }
-
-    /**
-     * Returns a human-readable log for given user event.
-     */
-    public static String generateLog(LauncherEvent ev) {
-        String log = "\n-----------------------------------------------------"
-                + "\naction:" + LoggerUtils.getActionStr(ev.action);
-        if (ev.srcTarget != null && ev.srcTarget.length > 0) {
-            log += "\n Source " + getTargetsStr(ev.srcTarget);
+        LauncherLogProto.LauncherEvent liteLauncherEvent;
+        try {
+            liteLauncherEvent =
+                    LauncherLogProto.LauncherEvent.parseFrom(MessageNano.toByteArray(ev));
+        } catch (InvalidProtocolBufferException e) {
+            throw new RuntimeException("Cannot parse LauncherEvent from Nano to Lite version");
         }
-        if (ev.destTarget != null && ev.destTarget.length > 0) {
-            log += "\n Destination " + getTargetsStr(ev.destTarget);
-        }
-        log += String.format(Locale.US,
-                "\n Elapsed container %d ms, session %d ms, action %d ms",
-                ev.elapsedContainerMillis,
-                ev.elapsedSessionMillis,
-                ev.actionDurationMillis);
-        log += "\n\n";
-        return log;
-    }
-
-    private static String getTargetsStr(Target[] targets) {
-        String result = "child:" + LoggerUtils.getTargetStr(targets[0]);
-        for (int i = 1; i < targets.length; i++) {
-            result += "\tparent:" + LoggerUtils.getTargetStr(targets[i]);
-        }
-        return result;
+        Log.d(TAG, liteLauncherEvent.toString());
     }
 
     /**
diff --git a/src/com/android/launcher3/model/PagedViewOrientedState.java b/src/com/android/launcher3/model/PagedViewOrientedState.java
index 1349eff..e48b8c1 100644
--- a/src/com/android/launcher3/model/PagedViewOrientedState.java
+++ b/src/com/android/launcher3/model/PagedViewOrientedState.java
@@ -50,7 +50,16 @@
      */
     private boolean mDisableMultipleOrientations;
 
+    /**
+     * Sets the appropriate {@link PagedOrientationHandler} for {@link #mOrientationHandler}
+     * @param touchRotation The rotation the nav bar region that is touched is in
+     * @param displayRotation Rotation of the display/device
+     */
     public void update(int touchRotation, int displayRotation) {
+        if (mDisableMultipleOrientations) {
+            return;
+        }
+
         mDisplayRotation = displayRotation;
         mTouchRotation = touchRotation;
         if (mTouchRotation == Surface.ROTATION_90) {
@@ -62,20 +71,13 @@
         }
     }
 
-    /**
-     * @return {@code true} if the area where the user touched the nav bar is the expected
-     * location for the given display rotation. Ex. bottom of phone in portrait, or left side of
-     * phone in landscape, right side in seascape, etc.
-     * False otherwise
-     */
-    public boolean isTouchRegionNaturalForDisplay() {
-        return mTouchRotation == mDisplayRotation;
-    }
-
     public boolean areMultipleLayoutOrientationsDisabled() {
         return mDisableMultipleOrientations;
     }
 
+    /**
+     * Setting this preference will render future calls to {@link #update(int, int)} as a no-op.
+     */
     public void disableMultipleOrientations(boolean disable) {
         mDisableMultipleOrientations = disable;
         if (disable) {
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 1c2acfd..18bc55a 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -240,6 +240,17 @@
      * and align above if there is enough vertical space.
      */
     protected void orientAboutObject() {
+        orientAboutObject(true /* allowAlignLeft */, true /* allowAlignRight */);
+    }
+
+    /**
+     * @see #orientAboutObject()
+     *
+     * @param allowAlignLeft Set to false if we already tried aligning left and didn't have room.
+     * @param allowAlignRight Set to false if we already tried aligning right and didn't have room.
+     * TODO: Can we test this with all permutations of widths/heights and icon locations + RTL?
+     */
+    private void orientAboutObject(boolean allowAlignLeft, boolean allowAlignRight) {
         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
         int width = getMeasuredWidth();
         int extraVerticalSpace = mArrow.getLayoutParams().height + mArrowOffset
@@ -253,14 +264,8 @@
         // Align left (right in RTL) if there is room.
         int leftAlignedX = mTempRect.left;
         int rightAlignedX = mTempRect.right - width;
-        int x = leftAlignedX;
-        boolean canBeLeftAligned = leftAlignedX + width + insets.left
-                < dragLayer.getRight() - insets.right;
-        boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
-        if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
-            x = rightAlignedX;
-        }
-        mIsLeftAligned = x == leftAlignedX;
+        mIsLeftAligned = !mIsRtl ? allowAlignLeft : !allowAlignRight;
+        int x = mIsLeftAligned ? leftAlignedX : rightAlignedX;
 
         // Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
         int iconWidth = mTempRect.width();
@@ -282,6 +287,24 @@
         }
         x += mIsLeftAligned ? xOffset : -xOffset;
 
+        // Check whether we can still align as we originally wanted, now that we've calculated x.
+        if (!allowAlignLeft && !allowAlignRight) {
+            // We've already tried both ways and couldn't make it fit. onLayout() will set the
+            // gravity to CENTER_HORIZONTAL, but continue below to update y.
+        } else {
+            boolean canBeLeftAligned = x + width + insets.left
+                    < dragLayer.getRight() - insets.right;
+            boolean canBeRightAligned = x > dragLayer.getLeft() + insets.left;
+            boolean alignmentStillValid = mIsLeftAligned && canBeLeftAligned
+                    || !mIsLeftAligned && canBeRightAligned;
+            if (!alignmentStillValid) {
+                // Try again, but don't allow this alignment we already know won't work.
+                orientAboutObject(allowAlignLeft && !mIsLeftAligned /* allowAlignLeft */,
+                        allowAlignRight && mIsLeftAligned /* allowAlignRight */);
+                return;
+            }
+        }
+
         // Open above icon if there is room.
         int iconHeight = mTempRect.height();
         int y = mTempRect.top - height;
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
index 8fffee8..936d377 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -54,6 +54,11 @@
 
     public SecondaryDragLayer(Context context, AttributeSet attrs) {
         super(context, attrs, 1 /* alphaChannelCount */);
+        recreateControllers();
+    }
+
+    @Override
+    public void recreateControllers() {
         mControllers = new TouchController[] {new CloseAllAppsTouchController()};
     }
 
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 43d54eb..fae0fe2 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -21,13 +21,13 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
 
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.config.FeatureFlags.FLAG_ENABLE_FIXED_ROTATION_TRANSFORM;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
 import android.content.ContentResolver;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.res.Resources;
-import android.database.ContentObserver;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -72,26 +72,13 @@
         return originalSmallestWidth >= 600;
     }
 
-
-    private final ContentObserver mContentObserver =
-        new ContentObserver(MAIN_EXECUTOR.getHandler()) {
-            @Override
-            public void onChange(boolean selfChange) {
-                boolean forcedRotation = Utilities.isForcedRotation(mLauncher);
-                PagedView.sFlagForcedRotation = forcedRotation;
-                updateForcedRotation();
-                for (ForcedRotationChangedListener listener : mForcedRotationChangedListeners) {
-                    listener.onForcedRotationChanged(forcedRotation);
-                }
-            }
-        };
-
     public static final int REQUEST_NONE = 0;
     public static final int REQUEST_ROTATE = 1;
     public static final int REQUEST_LOCK = 2;
 
     private final Launcher mLauncher;
-    private final SharedPreferences mPrefs;
+    private final SharedPreferences mSharedPrefs;
+    private final SharedPreferences mFeatureFlagsPrefs;
 
     private boolean mIgnoreAutoRotateSettings;
     private boolean mAutoRotateEnabled;
@@ -125,24 +112,42 @@
 
         // On large devices we do not handle auto-rotate differently.
         mIgnoreAutoRotateSettings = mLauncher.getResources().getBoolean(R.bool.allow_rotation);
-        updateForcedRotation();
         if (!mIgnoreAutoRotateSettings) {
-            mPrefs = Utilities.getPrefs(mLauncher);
-            mPrefs.registerOnSharedPreferenceChangeListener(this);
-            mAutoRotateEnabled = mPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+            mSharedPrefs = Utilities.getPrefs(mLauncher);
+            mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
+            mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
                     getAllowRotationDefaultValue());
         } else {
-            mPrefs = null;
+            mSharedPrefs = null;
         }
 
-        // TODO(b/150260456) Add this in home settings as well
         mContentResolver = launcher.getContentResolver();
-        mContentResolver.registerContentObserver(Settings.Global.getUriFor(
-            FIXED_ROTATION_TRANSFORM_SETTING_NAME), false, mContentObserver);
+        mFeatureFlagsPrefs = Utilities.getFeatureFlagsPrefs(mLauncher);
+        mFeatureFlagsPrefs.registerOnSharedPreferenceChangeListener(this);
+        updateForcedRotation(true);
     }
 
-    private void updateForcedRotation() {
-        mForcedRotation = !getAllowRotationDefaultValue() && Utilities.isForcedRotation(mLauncher);
+    /**
+     * @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;
+        }
+        if (setValueFromPrefs) {
+            mForcedRotation = isForcedRotation;
+        }
+        UI_HELPER_EXECUTOR.execute(
+                () -> Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME,
+                        mForcedRotation ? 1 : 0));
+        for (ForcedRotationChangedListener listener : mForcedRotationChangedListeners) {
+            listener.onForcedRotationChanged(mForcedRotation);
+        }
     }
 
     /**
@@ -181,8 +186,13 @@
 
     @Override
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+        if (FLAG_ENABLE_FIXED_ROTATION_TRANSFORM.equals(s)) {
+            updateForcedRotation(true);
+            return;
+        }
+
         boolean wasRotationEnabled = mAutoRotateEnabled;
-        mAutoRotateEnabled = mPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+        mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
                 getAllowRotationDefaultValue());
         if (mAutoRotateEnabled != wasRotationEnabled) {
 
@@ -218,6 +228,10 @@
     public void forceAllowRotationForTesting(boolean allowRotation) {
         mIgnoreAutoRotateSettings =
                 allowRotation || mLauncher.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();
     }
 
@@ -232,13 +246,11 @@
     public void destroy() {
         if (!mDestroyed) {
             mDestroyed = true;
-            if (mPrefs != null) {
-                mPrefs.unregisterOnSharedPreferenceChangeListener(this);
-            }
-            if (mContentResolver != null) {
-                mContentResolver.unregisterContentObserver(mContentObserver);
+            if (mSharedPrefs != null) {
+                mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
             }
             mForcedRotationChangedListeners.clear();
+            mFeatureFlagsPrefs.unregisterOnSharedPreferenceChangeListener(this);
         }
     }
 
@@ -293,7 +305,7 @@
         return degrees;
     }
 
-    public static int getRotationFromDegrees(int degrees) {
+    public static int getRotationFromDegrees(float degrees) {
         int threshold = 70;
         if (degrees >= (360 - threshold) || degrees < (threshold)) {
             return Surface.ROTATION_0;
@@ -318,11 +330,30 @@
     }
 
     /**
+     * For landscape, since the navbar is already in a vertical position, we don't have to do any
+     * rotations as the change in Y coordinate is what is read. We only flip the sign of the
+     * y coordinate to make it match existing behavior of swipe to the top to go previous
+     */
+    public static void transformEventForNavBar(MotionEvent ev, boolean inverse) {
+        // TODO(b/151269990): Use a temp matrix
+        Matrix m = new Matrix();
+        m.setScale(1, -1);
+        if (inverse) {
+            Matrix inv = new Matrix();
+            m.invert(inv);
+            ev.transform(inv);
+        } else {
+            ev.transform(m);
+        }
+    }
+
+    /**
      * Creates a matrix to transform the given motion event specified by degrees.
      * If {@param inverse} is {@code true}, the inverse of that matrix will be applied
      */
     public static void transformEvent(float degrees, MotionEvent ev, boolean inverse) {
         Matrix transform = new Matrix();
+        // TODO(b/151269990): Use a temp matrix
         transform.setRotate(degrees);
         if (inverse) {
             Matrix inv = new Matrix();
@@ -344,6 +375,7 @@
      */
     public static Matrix getRotationMatrix(int screenWidth, int screenHeight, int displayRotation) {
         Matrix m = new Matrix();
+        // TODO(b/151269990): Use a temp matrix
         switch (displayRotation) {
             case Surface.ROTATION_0:
                 return m;
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 4e98781..c3664c3 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -19,9 +19,9 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
-import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_SCALE_COMPONENT;
-import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL_COMPONENTS;
+import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_SCALE;
+import static com.android.launcher3.LauncherStateManager.PLAY_NON_ATOMIC;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
 import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
@@ -37,7 +37,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationComponents;
+import com.android.launcher3.LauncherStateManager.AnimationFlags;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -176,7 +176,7 @@
     protected abstract LauncherState getTargetState(LauncherState fromState,
             boolean isDragTowardPositive);
 
-    protected abstract float initCurrentAnimation(@AnimationComponents int animComponents);
+    protected abstract float initCurrentAnimation(@AnimationFlags int animComponents);
 
     /**
      * Returns the container that the touch started from when leaving NORMAL state.
@@ -201,10 +201,10 @@
             mCurrentAnimation.setOnCancelRunnable(null);
         }
         int animComponents = goingBetweenNormalAndOverview(mFromState, mToState)
-                ? NON_ATOMIC_COMPONENT : ANIM_ALL;
+                ? PLAY_NON_ATOMIC : ANIM_ALL_COMPONENTS;
         mScheduleResumeAtomicComponent = false;
         if (mAtomicAnim != null) {
-            animComponents = NON_ATOMIC_COMPONENT;
+            animComponents = PLAY_NON_ATOMIC;
             // Control the non-atomic components until the atomic animation finishes, then control
             // the atomic components as well.
             mScheduleResumeAtomicComponent = true;
@@ -215,7 +215,7 @@
         }
 
         if (mAtomicComponentsController != null) {
-            animComponents &= ~ATOMIC_OVERVIEW_SCALE_COMPONENT;
+            animComponents &= ~PLAY_ATOMIC_OVERVIEW_SCALE;
         }
         mProgressMultiplier = initCurrentAnimation(animComponents);
         mCurrentAnimation.dispatchOnStart();
@@ -360,7 +360,7 @@
             long duration) {
         AnimatorSetBuilder builder = getAnimatorSetBuilderForStates(fromState, targetState);
         return mLauncher.getStateManager().createAtomicAnimation(fromState, targetState, builder,
-                ATOMIC_OVERVIEW_SCALE_COMPONENT, duration);
+                PLAY_ATOMIC_OVERVIEW_SCALE, duration);
     }
 
     protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index 31a5d79..8d5f33d 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -23,7 +23,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationComponents;
+import com.android.launcher3.LauncherStateManager.AnimationFlags;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
@@ -76,7 +76,7 @@
     }
 
     @Override
-    protected float initCurrentAnimation(@AnimationComponents int animComponents) {
+    protected float initCurrentAnimation(@AnimationFlags int animComponents) {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);
         mCurrentAnimation = mLauncher.getStateManager()
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 254655c..25748ae 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -40,7 +40,6 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
@@ -116,6 +115,11 @@
     }
 
     /**
+     * Called to reinitialize touch controllers.
+     */
+    public abstract void recreateControllers();
+
+    /**
      * Same as {@link #isEventOverView(View, MotionEvent, View)} where evView == this drag layer.
      */
     public boolean isEventOverView(View view, MotionEvent ev) {
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
index 81f8327..d849138 100644
--- a/src/com/android/launcher3/views/WorkEduView.java
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -46,7 +46,8 @@
 public class WorkEduView extends AbstractSlideInView implements Insettable {
 
     private static final int DEFAULT_CLOSE_DURATION = 200;
-    private static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
+    public static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
+    public static final String KEY_LEGACY_WORK_EDU_SEEN = "showed_bottom_user_education";
 
     private static final int WORK_EDU_NOT_STARTED = 0;
     private static final int WORK_EDU_PERSONAL_APPS = 1;
@@ -102,6 +103,8 @@
         mProceedButton = findViewById(R.id.proceed);
         mContentText = findViewById(R.id.content_text);
 
+        // make sure layout does not shrink when we change the text
+        mContentText.post(() -> mContentText.setMinLines(mContentText.getLineCount()));
         if (mLauncher.getAppsView().getContentView() instanceof AllAppsPagedView) {
             mAllAppsPagedView = (AllAppsPagedView) mLauncher.getAppsView().getContentView();
         }
@@ -179,8 +182,8 @@
         if (oldListener != null) {
             launcher.getStateManager().removeStateListener(oldListener);
         }
-        if (launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP, WORK_EDU_NOT_STARTED)
-                != WORK_EDU_NOT_STARTED) {
+        if (hasSeenLegacyEdu(launcher) || launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP,
+                WORK_EDU_NOT_STARTED) != WORK_EDU_NOT_STARTED) {
             return null;
         }
 
@@ -210,8 +213,8 @@
      * Shows work apps edu if user had dismissed full edu flow
      */
     public static void showWorkEduIfNeeded(Launcher launcher) {
-        if (launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP, WORK_EDU_NOT_STARTED)
-                != WORK_EDU_PERSONAL_APPS) {
+        if (hasSeenLegacyEdu(launcher) || launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP,
+                WORK_EDU_NOT_STARTED) != WORK_EDU_PERSONAL_APPS) {
             return;
         }
         LayoutInflater layoutInflater = LayoutInflater.from(launcher);
@@ -220,4 +223,8 @@
         v.show();
         v.goToWorkTab(false);
     }
+
+    private static boolean hasSeenLegacyEdu(Launcher launcher) {
+        return launcher.getSharedPrefs().getBoolean(KEY_LEGACY_WORK_EDU_SEEN, false);
+    }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java b/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
new file mode 100644
index 0000000..4913cad
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides;
+
+import android.content.Context;
+import android.os.Bundle;
+
+/** Render preview using surface view. */
+public class PreviewSurfaceRenderer {
+
+    /** Handle a received surface view request. */
+    public static void render(Context context, Bundle bundle) { }
+}
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index 6fe6739..db2d974 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -16,8 +16,6 @@
 package com.android.launcher3.ui;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_POSTSUBMIT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -25,13 +23,16 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.widget.TextView;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsPagedView;
-import com.android.launcher3.util.rule.TestStabilityRule;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.views.WorkEduView;
 import com.android.launcher3.views.WorkFooterContainer;
 
 import org.junit.After;
@@ -48,6 +49,8 @@
 
     private int mProfileUserId;
 
+    private static final int WORK_PAGE = AllAppsContainerView.AdapterHolder.WORK;
+
     @Before
     public void createWorkProfile() throws Exception {
         String output =
@@ -67,8 +70,6 @@
     }
 
     @Test
-    // b/143285809 Remove @Stability on 02/21/20 if the test doesn't flake.
-    @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT)
     public void workTabExists() {
         mDevice.pressHome();
         waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
@@ -80,8 +81,6 @@
     }
 
     @Test
-    // b/143285809 Remove @Stability on 02/21/20 if the test doesn't flake.
-    @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT)
     public void toggleWorks() {
         mDevice.pressHome();
         waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
@@ -112,4 +111,78 @@
                 l -> l.getSystemService(UserManager.class).isQuietModeEnabled(workProfile));
     }
 
+    @Test
+    public void testWorkEduFlow() {
+        mDevice.pressHome();
+        waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
+        executeOnLauncher(launcher -> launcher.getSharedPrefs().edit().remove(
+                WorkEduView.KEY_WORK_EDU_STEP).remove(
+                WorkEduView.KEY_LEGACY_WORK_EDU_SEEN).commit());
+
+        waitForLauncherCondition("Work tab not setup",
+                launcher -> launcher.getAppsView().getContentView() instanceof AllAppsPagedView,
+                60000);
+
+        executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+        WorkEduView workEduView = getEduView();
+        // verify personal app edu is seen first and click "next"
+        executeOnLauncher(l -> {
+            assertEquals(((TextView) workEduView.findViewById(R.id.content_text)).getText(),
+                    l.getResources().getString(R.string.work_profile_edu_personal_apps));
+            workEduView.findViewById(R.id.proceed).callOnClick();
+        });
+        // verify work edu is seen next
+        waitForLauncherCondition("Launcher did not show the next edu screen", l ->
+                ((AllAppsPagedView) l.getAppsView().getContentView()).getCurrentPage() == WORK_PAGE
+                        && ((TextView) workEduView.findViewById(
+                        R.id.content_text)).getText().equals(
+                        l.getResources().getString(R.string.work_profile_edu_work_apps)));
+    }
+
+    @Test
+    public void testWorkEduIntermittent() {
+        mDevice.pressHome();
+        waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
+        executeOnLauncher(launcher -> launcher.getSharedPrefs().edit().remove(
+                WorkEduView.KEY_WORK_EDU_STEP).remove(
+                WorkEduView.KEY_LEGACY_WORK_EDU_SEEN).commit());
+
+
+        waitForLauncherCondition("Work tab not setup",
+                launcher -> launcher.getAppsView().getContentView() instanceof AllAppsPagedView,
+                60000);
+        executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+
+        // verify personal app edu is seen
+        getEduView();
+
+        // dismiss personal edu
+        mDevice.pressHome();
+
+        // open work tab
+        executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+        executeOnLauncher(launcher -> {
+            AllAppsPagedView pagedView = (AllAppsPagedView) launcher.getAppsView().getContentView();
+            pagedView.setCurrentPage(WORK_PAGE);
+        });
+
+        WorkEduView workEduView = getEduView();
+
+        // verify work tab edu is shown
+        waitForLauncherCondition("Launcher did not show the next edu screen",
+                l -> ((TextView) workEduView.findViewById(R.id.content_text)).getText().equals(
+                        l.getResources().getString(R.string.work_profile_edu_work_apps)));
+    }
+
+
+    private WorkEduView getEduView() {
+        waitForLauncherCondition("Edu did not show", l -> {
+            DragLayer dragLayer = l.getDragLayer();
+            return dragLayer.getChildCount() > 0 && dragLayer.getChildAt(
+                    dragLayer.getChildCount() - 1) instanceof WorkEduView;
+        });
+        return getFromLauncher(launcher -> (WorkEduView) launcher.getDragLayer().getChildAt(
+                launcher.getDragLayer().getChildCount() - 1));
+    }
+
 }
\ No newline at end of file