Merge "Optimization: reducing number of getNavigationModeMismatchError calls" into ub-launcher3-rvc-dev
diff --git a/quickstep/recents_ui_overrides/res/drawable/chip_scrim_gradient.xml b/quickstep/recents_ui_overrides/res/drawable/chip_scrim_gradient.xml
new file mode 100644
index 0000000..5a2dfb7
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/drawable/chip_scrim_gradient.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient
+        android:angle="90"
+        android:endColor="@android:color/transparent"
+        android:startColor="@color/chip_scrim_start_color"
+        android:type="linear" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/colors.xml b/quickstep/recents_ui_overrides/res/values/colors.xml
index 361f5f7..f03f118 100644
--- a/quickstep/recents_ui_overrides/res/values/colors.xml
+++ b/quickstep/recents_ui_overrides/res/values/colors.xml
@@ -15,6 +15,7 @@
 -->
 <resources>
     <color name="chip_hint_foreground_color">#fff</color>
+    <color name="chip_scrim_start_color">#39000000</color>
 
     <color name="all_apps_label_text">#61000000</color>
     <color name="all_apps_label_text_dark">#61FFFFFF</color>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 87ca2b6..e074b03 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 
 import android.content.Intent;
@@ -219,7 +220,12 @@
 
         @Override
         protected boolean isRecentsInteractive() {
-            return mActivity.isInState(OVERVIEW);
+            return mActivity.isInState(OVERVIEW) || mActivity.isInState(OVERVIEW_MODAL_TASK);
+        }
+
+        @Override
+        protected boolean isRecentsModal() {
+            return mActivity.isInState(OVERVIEW_MODAL_TASK);
         }
 
         @Override
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 2f55fda..a1cc60e 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
@@ -19,6 +19,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
 
 import android.annotation.TargetApi;
 import android.os.Build;
@@ -88,6 +89,11 @@
     }
 
     @Override
+    FloatProperty<RecentsView> getTaskModalnessProperty() {
+        return TASK_MODALNESS;
+    }
+
+    @Override
     FloatProperty<RecentsView> getContentAlphaProperty() {
         return CONTENT_ALPHA;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
new file mode 100644
index 0000000..b238200
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -0,0 +1,69 @@
+/*
+ * 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.states;
+
+import android.content.res.Resources;
+import android.graphics.Rect;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * An Overview state that shows the current task in a modal fashion. Modal state is where the
+ * current task is shown on its own without other tasks visible.
+ */
+public class OverviewModalTaskState extends OverviewState {
+
+    private static final int STATE_FLAGS =
+            FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY;
+
+    public OverviewModalTaskState(int id) {
+        super(id, ContainerType.OVERVIEW, STATE_FLAGS);
+    }
+
+    @Override
+    public int getTransitionDuration(Launcher launcher) {
+        return 100;
+    }
+
+    @Override
+    public int getVisibleElements(Launcher launcher) {
+        return OVERVIEW_BUTTONS;
+    }
+
+    @Override
+    public float[] getOverviewScaleAndOffset(Launcher launcher) {
+        Resources res = launcher.getBaseContext().getResources();
+
+        Rect out = new Rect();
+        launcher.<RecentsView>getOverviewPanel().getTaskSize(out);
+        int taskHeight = out.height();
+
+        float topMargin = res.getDimension(R.dimen.task_thumbnail_top_margin);
+        float bottomMargin = res.getDimension(R.dimen.task_thumbnail_bottom_margin_with_actions);
+        float newHeight = taskHeight + topMargin + bottomMargin;
+        float scale = newHeight / taskHeight;
+
+        return new float[] {scale, 0};
+    }
+
+    @Override
+    public float getOverviewModalness() {
+        return 1.0f;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index e44f59f..fad9ea5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -259,4 +259,11 @@
     public static OverviewState newSwitchState(int id) {
         return new QuickSwitchState(id);
     }
+
+    /**
+     *  New Overview substate that represents the overview in modal mode (one task shown on its own)
+     */
+    public static OverviewState newModalTaskState(int id) {
+        return new OverviewModalTaskState(id);
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 2b456ec..06a481b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -17,7 +17,6 @@
 
 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.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
@@ -99,7 +98,7 @@
         if (!cameFromNavBar) {
             return false;
         }
-        if (mStartState == OVERVIEW || mStartState == ALL_APPS) {
+        if (mStartState.overviewUi || mStartState == ALL_APPS) {
             return true;
         }
         if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
@@ -129,7 +128,7 @@
     private void initCurrentAnimation() {
         long accuracy = (long) (getShiftRange() * 2);
         final PendingAnimation builder = new PendingAnimation(accuracy);
-        if (mStartState == OVERVIEW) {
+        if (mStartState.overviewUi) {
             RecentsView recentsView = mLauncher.getOverviewPanel();
             builder.setFloat(recentsView, ADJACENT_PAGE_OFFSET,
                     -mPullbackDistance / recentsView.getPageOffsetScale(), PULLBACK_INTERPOLATOR);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index f6f892b..1f3b82c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -96,6 +96,9 @@
 
     protected abstract boolean isRecentsInteractive();
 
+    /** Is recents view showing a single task in a modal way. */
+    protected abstract boolean isRecentsModal();
+
     protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
     }
 
@@ -134,7 +137,7 @@
                     if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer()
                             .isEventOverView(view, ev)) {
                         // Disable swiping up and down if the task overlay is modal.
-                        if (view.isTaskOverlayModal()) {
+                        if (isRecentsModal()) {
                             mTaskBeingDragged = null;
                             break;
                         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index 147f933..b44d6df 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -131,13 +131,6 @@
         }
 
         /**
-         * Whether the overlay is modal, which means only tapping is enabled, but no swiping.
-         */
-        public boolean isOverlayModal() {
-            return false;
-        }
-
-        /**
          * Gets the task snapshot as it is displayed on the screen.
          *
          * @return the bounds of the snapshot in screen coordinates.
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java
index a113604..d7458d2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java
@@ -28,4 +28,9 @@
     protected boolean isRecentsInteractive() {
         return mActivity.hasWindowFocus();
     }
+
+    @Override
+    protected boolean isRecentsModal() {
+        return false;
+    }
 }
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 13c20f1..820bd17 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
@@ -190,6 +190,7 @@
                 .setStiffness(stiffness)
                 .setDampingRatio(damping)
                 .setMinimumVisibleChange(1f)
+                .setStartValue(mSpringTransY)
                 .setEndValue(0)
                 .setStartVelocity(mVelocity)
                 .build(v, VIEW_TRANSLATE_Y);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
index 0bc021b..9781300 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -57,6 +57,7 @@
 
     private final Rect mTmpCropRect = new Rect();
     private final RectF mTempRectF = new RectF();
+    private final float[] mTempPoint = new float[2];
 
     private final RecentsOrientedState mOrientationState;
     private final Context mContext;
@@ -267,7 +268,7 @@
                     builder.withAlpha(alpha)
                             .withMatrix(mMatrix)
                             .withWindowCrop(mTmpCropRect)
-                            .withCornerRadius(mCurrentFullscreenParams.mCurrentDrawnCornerRadius);
+                            .withCornerRadius(getCurrentCornerRadius());
                 } else if (params.getTargetSet().hasRecents) {
                     // If home has a different target then recents, reverse anim the home target.
                     builder.withAlpha(fullScreenProgress.value * params.getTargetAlpha());
@@ -285,6 +286,20 @@
     }
 
     /**
+     * Returns the corner radius that should be applied to the target so that it matches the
+     * TaskView
+     */
+    public float getCurrentCornerRadius() {
+        float visibleRadius = mCurrentFullscreenParams.mCurrentDrawnCornerRadius;
+        mTempPoint[0] = visibleRadius;
+        mTempPoint[1] = 0;
+        mInversePositionMatrix.mapVectors(mTempPoint);
+
+        // Ideally we should use square-root. This is an optimization as one of the dimension is 0.
+        return Math.max(Math.abs(mTempPoint[0]), Math.abs(mTempPoint[1]));
+    }
+
+    /**
      * Interface for calculating taskSize
      */
     public interface TaskSizeProvider {
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 0b6d340..9005651 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
@@ -20,6 +20,7 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
+import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
@@ -284,7 +285,7 @@
             // Clean-up logic that occurs when recents is no longer in use/visible.
             reset();
         }
-        setOverlayEnabled(finalState == OVERVIEW);
+        setOverlayEnabled(finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK);
         setFreezeViewVisibility(false);
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
index 6e9ca23..d160686 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
@@ -17,7 +17,7 @@
 package com.android.quickstep.views;
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.quickstep.SysUINavigationMode.getMode;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.content.Context;
 import android.util.AttributeSet;
@@ -31,7 +31,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
-import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
 
 import java.lang.annotation.Retention;
@@ -119,8 +118,7 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         updateHiddenFlags(HIDDEN_DISABLED_FEATURE, !ENABLE_OVERVIEW_ACTIONS.get());
-        updateHiddenFlags(HIDDEN_UNSUPPORTED_NAVIGATION,
-                getMode(getContext()) == SysUINavigationMode.Mode.TWO_BUTTONS);
+        updateHiddenFlags(HIDDEN_UNSUPPORTED_NAVIGATION, !removeShelfFromOverview(getContext()));
     }
 
     public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
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 fc42acf..cd3abed 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
@@ -70,7 +70,6 @@
 import android.text.TextPaint;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
-import android.util.Log;
 import android.util.Property;
 import android.util.SparseBooleanArray;
 import android.view.HapticFeedbackConstants;
@@ -117,7 +116,6 @@
 import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
-import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
@@ -174,13 +172,26 @@
                 }
             };
 
+    public static final FloatProperty<RecentsView> TASK_MODALNESS =
+            new FloatProperty<RecentsView>("taskModalness") {
+                @Override
+                public void setValue(RecentsView recentsView, float v) {
+                    recentsView.setTaskModalness(v);
+                }
+
+                @Override
+                public Float get(RecentsView recentsView) {
+                    return recentsView.mTaskModalness;
+                }
+            };
+
     public static final FloatProperty<RecentsView> ADJACENT_PAGE_OFFSET =
             new FloatProperty<RecentsView>("adjacentPageOffset") {
                 @Override
                 public void setValue(RecentsView recentsView, float v) {
                     if (recentsView.mAdjacentPageOffset != v) {
                         recentsView.mAdjacentPageOffset = v;
-                        recentsView.updateAdjacentPageOffset();
+                        recentsView.updatePageOffsets();
                     }
                 }
 
@@ -328,6 +339,12 @@
     protected float mContentAlpha = 1;
     @ViewDebug.ExportedProperty(category = "launcher")
     protected float mFullscreenProgress = 0;
+    /**
+     * How modal is the current task to be displayed, 1 means the task is fully modal and no other
+     * tasks are show. 0 means the task is displays in context in the list with other tasks.
+     */
+    @ViewDebug.ExportedProperty(category = "launcher")
+    protected float mTaskModalness = 0;
 
     // Keeps track of task id whose visual state should not be reset
     private int mIgnoreResetTaskId = -1;
@@ -648,7 +665,7 @@
     @Override
     protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
         // Enables swiping to the left or right only if the task overlay is not modal.
-        if (getCurrentPageTaskView() == null || !getCurrentPageTaskView().isTaskOverlayModal()) {
+        if (mTaskModalness == 0f) {
             super.determineScrollingStart(ev, touchSlopScale);
         }
     }
@@ -736,25 +753,6 @@
         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);
-        float alpha = isTaskOverlayModal ? 0.0f : 1.0f;
-        if (previousTask != null) {
-            previousTask.animate().alpha(alpha)
-                    .translationX(isTaskOverlayModal ? previousTask.getWidth() / 2 : 0);
-        }
-        if (nextTask != null) {
-            nextTask.animate().alpha(alpha)
-                    .translationX(isTaskOverlayModal ? -nextTask.getWidth() / 2 : 0);
-
-        }
-    }
-
     protected void onTaskStackUpdated() { }
 
     public void resetTaskVisuals() {
@@ -777,6 +775,7 @@
         updateCurveProperties();
         // Update the set of visible task's data
         loadVisibleTaskData();
+        setTaskModalness(0);
     }
 
     public void setFullscreenProgress(float fullscreenProgress) {
@@ -802,14 +801,11 @@
     @Override
     public void setInsets(Rect insets) {
         mInsets.set(insets);
-        resetPaddingFromTaskSize();
-    }
-
-    private void resetPaddingFromTaskSize() {
         DeviceProfile dp = mActivity.getDeviceProfile();
         getTaskSize(dp, mTempRect);
         mTaskWidth = mTempRect.width();
         mTaskHeight = mTempRect.height();
+
         mTempRect.top -= mTaskTopMargin;
         setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
                 dp.widthPx - mInsets.right - mTempRect.right,
@@ -1575,7 +1571,6 @@
             mActivity.getDragLayer().recreateControllers();
             mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
                     touchRotation != 0 || launcherRotation != 0);
-            resetPaddingFromTaskSize();
             requestLayout();
         }
     }
@@ -1658,21 +1653,27 @@
                 mTempRect, mActivity.getDeviceProfile(), mTempPointF);
         setPivotX(mTempPointF.x);
         setPivotY(mTempPointF.y);
-        updateAdjacentPageOffset();
+        updatePageOffsets();
     }
 
-    private void updateAdjacentPageOffset() {
+    private void updatePageOffsets() {
         float offset = mAdjacentPageOffset * getWidth();
+        float modalOffset = mTaskModalness * getWidth();
         if (mIsRtl) {
             offset = -offset;
+            modalOffset = -modalOffset;
         }
         int count = getChildCount();
 
         TaskView runningTask = mRunningTaskId == -1 ? null : getTaskView(mRunningTaskId);
         int midPoint = runningTask == null ? -1 : indexOfChild(runningTask);
+        int currentPage = getCurrentPage();
 
         for (int i = 0; i < count; i++) {
-            getChildAt(i).setTranslationX(i == midPoint ? 0 : (i < midPoint ? -offset : offset));
+            float translation = i == midPoint ? 0 : (i < midPoint ? -offset : offset);
+            float modalTranslation =
+                    i == currentPage ? 0 : (i < currentPage ? -modalOffset : modalOffset);
+            getChildAt(i).setTranslationX(translation + modalTranslation);
         }
         updateCurveProperties();
     }
@@ -2118,6 +2119,18 @@
         }
     }
 
+    /**
+     * The current task is fully modal (modalness = 1) when it is shown on its own in a modal
+     * way. Modalness 0 means the task is shown in context with all the other tasks.
+     */
+    private void setTaskModalness(float modalness) {
+        mTaskModalness = modalness;
+        updatePageOffsets();
+        if (getCurrentPageTaskView() != null) {
+            getCurrentPageTaskView().setModalness(modalness);
+        }
+    }
+
     @Nullable
     protected DepthController getDepthController() {
         return null;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 8dc41d0..e525842 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -50,7 +50,6 @@
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
-import com.android.quickstep.util.RecentsOrientedState;
 import com.android.systemui.plugins.OverviewScreenshotActions;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.recents.model.Task;
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 ad49bfa..aea5b8e 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
@@ -25,6 +25,7 @@
 import static android.widget.Toast.LENGTH_SHORT;
 
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.Utilities.comp;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
@@ -42,6 +43,8 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.InsetDrawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Process;
@@ -163,10 +166,10 @@
     private ObjectAnimator mIconAndDimAnimator;
     private float mIconScaleAnimStartProgress = 0;
     private float mFocusTransitionProgress = 1;
+    private float mModalness = 0;
     private float mStableAlpha = 1;
 
     private boolean mShowScreenshot;
-    private boolean mRunningModalAnimation = false;
 
     // The current background requests to load the task thumbnail and icon
     private TaskThumbnailCache.ThumbnailLoadRequest mThumbnailLoadRequest;
@@ -237,52 +240,24 @@
         mIconView = findViewById(R.id.icon);
     }
 
-    public boolean isTaskOverlayModal() {
-        return mSnapshotView.getTaskOverlay().isOverlayModal();
-    }
-
-    /** Updates UI based on whether the task is modal. */
-    public void updateUiForModalTask() {
-        boolean isOverlayModal = isTaskOverlayModal();
-        mRunningModalAnimation = true;
-        if (getRecentsView() != null) {
-            getRecentsView().updateUiForModalTask(this, isOverlayModal);
+    /**
+     * The modalness of this view is how it should be displayed when it is shown on its own in the
+     * modal state of overview.
+     *
+     * @param modalness [0, 1] 0 being in context with other tasks, 1 being shown on its own.
+     */
+    public void setModalness(float modalness) {
+        mModalness = modalness;
+        mIconView.setAlpha(comp(modalness));
+        if (mContextualChip != null) {
+            mContextualChip.setScaleX(comp(modalness));
+            mContextualChip.setScaleY(comp(modalness));
+        }
+        if (mContextualChipWrapper != null) {
+            mContextualChipWrapper.setAlpha(comp(modalness));
         }
 
-        // Hides footers and icon when overlay is modal.
-        if (isOverlayModal) {
-            for (FooterWrapper footer : mFooters) {
-                if (footer != null) {
-                    footer.animateHide();
-                }
-            }
-            if (mContextualChip != null) {
-                mContextualChip.animate().scaleX(0f).scaleY(0f).setDuration(300);
-            }
-            mIconView.animate().alpha(0.0f);
-        } else {
-            if (mContextualChip != null) {
-                mContextualChip.animate().scaleX(1f).scaleY(1f).setDuration(300);
-            }
-            mIconView.animate().alpha(1.0f);
-        }
-
-        // Sets animations for modal UI. We will remove the margins to zoom in the snapshot.
-        float topMargin = getResources().getDimension(R.dimen.task_thumbnail_top_margin);
-        float bottomMargin =
-                getResources().getDimension(R.dimen.task_thumbnail_bottom_margin_with_actions);
-        float newHeight = mSnapshotView.getHeight() + topMargin + bottomMargin;
-        float scale = isOverlayModal ? newHeight / mSnapshotView.getHeight() : 1.0f;
-        float centerDifference = (bottomMargin - topMargin) / 2;
-        float translationY = isOverlayModal ? centerDifference : 0;
-        this.animate().scaleX(scale).scaleY(scale).translationY(translationY)
-                .withEndAction(new Runnable() {
-                    @Override
-                    public void run() {
-                        setCurveScale(scale);
-                        mRunningModalAnimation = false;
-                    }
-                });
+        updateFooterVerticalOffset(mFooterVerticalOffset);
     }
 
     public TaskMenuView getMenuView() {
@@ -526,12 +501,7 @@
         mIconView.setScaleX(scale);
         mIconView.setScaleY(scale);
 
-        mFooterVerticalOffset = 1.0f - scale;
-        for (FooterWrapper footer : mFooters) {
-            if (footer != null) {
-                footer.updateFooterOffset();
-            }
-        }
+        updateFooterVerticalOffset(1.0f - scale);
     }
 
     public void setIconScaleAnimStartProgress(float startProgress) {
@@ -577,6 +547,7 @@
     public void resetVisualProperties() {
         resetViewTransforms();
         setFullscreenProgress(0);
+        setModalness(0);
     }
 
     public void setStableAlpha(float parentAlpha) {
@@ -597,7 +568,7 @@
     @Override
     public void onPageScroll(ScrollState scrollState) {
         // Don't do anything if it's modal.
-        if (mRunningModalAnimation || isTaskOverlayModal()) {
+        if (mModalness > 0) {
             return;
         }
 
@@ -690,20 +661,33 @@
             LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
                     LayoutParams.WRAP_CONTENT);
             layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL;
+            int expectedChipHeight = getExpectedViewHeight(view);
+            float chipOffset = getResources().getDimension(R.dimen.chip_hint_vertical_offset);
             layoutParams.bottomMargin = (int)
                     (((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin
-                            - getExpectedViewHeight(view) + getResources().getDimension(
-                            R.dimen.chip_hint_vertical_offset));
+                            - expectedChipHeight + chipOffset);
             mContextualChip = ((FrameLayout) mContextualChipWrapper).getChildAt(0);
             mContextualChip.setScaleX(0f);
             mContextualChip.setScaleY(0f);
+            GradientDrawable scrimDrawable = (GradientDrawable) getResources().getDrawable(
+                    R.drawable.chip_scrim_gradient, mActivity.getTheme());
+            float cornerRadius = TaskCornerRadius.get(mActivity);
+            scrimDrawable.setCornerRadii(
+                    new float[]{0, 0, 0, 0, cornerRadius, cornerRadius, cornerRadius,
+                            cornerRadius});
+            InsetDrawable scrimDrawableInset = new InsetDrawable(scrimDrawable, 0, 0, 0,
+                    (int) (expectedChipHeight - chipOffset));
+            mContextualChipWrapper.setBackground(scrimDrawableInset);
+            mContextualChipWrapper.setPadding(0, 0, 0, 0);
+            mContextualChipWrapper.setAlpha(0f);
             addView(view, getChildCount(), layoutParams);
-            view.setAlpha(mFooterAlpha);
             if (mContextualChip != null) {
                 mContextualChip.animate().scaleX(1f).scaleY(1f).setDuration(50);
             }
+            if (mContextualChipWrapper != null) {
+                mContextualChipWrapper.animate().alpha(1f).setDuration(50);
+            }
         }
-
     }
 
     /**
@@ -737,6 +721,12 @@
                 mStackHeight += footer.mView.getHeight();
             }
         }
+        updateFooterVerticalOffset(0);
+    }
+
+    private void updateFooterVerticalOffset(float offset) {
+        mFooterVerticalOffset = offset;
+
         for (FooterWrapper footer : mFooters) {
             if (footer != null) {
                 footer.updateFooterOffset();
@@ -835,7 +825,8 @@
         }
 
         void updateFooterOffset() {
-            mAnimationOffset = Math.round(mStackHeight * mFooterVerticalOffset);
+            float offset = Utilities.or(mFooterVerticalOffset, mModalness);
+            mAnimationOffset = Math.round(mStackHeight * offset);
             mView.setTranslationY(mAnimationOffset + mEntryAnimationOffset
                     + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom
                     + mCurrentFullscreenParams.mCurrentDrawnInsets.top);
@@ -858,22 +849,6 @@
             animator.setDuration(100);
             animator.start();
         }
-
-        void animateHide() {
-            ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
-            animator.addUpdateListener(anim -> {
-                mFooterVerticalOffset = anim.getAnimatedFraction();
-                updateFooterOffset();
-            });
-            animator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    removeView(mView);
-                }
-            });
-            animator.setDuration(100);
-            animator.start();
-        }
     }
 
     private int getExpectedViewHeight(View view) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 33011ac..ac50d6d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
@@ -66,6 +67,7 @@
         getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
         OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
         SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
+        getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
     }
 
     @Override
@@ -101,8 +103,15 @@
         OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
         setter.setFloat(scrim, SCRIM_PROGRESS, toState.getOverviewScrimAlpha(mLauncher),
                 config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
+
+        setter.setFloat(
+                mRecentsView, getTaskModalnessProperty(),
+                toState.getOverviewModalness(),
+                config.getInterpolator(ANIM_OVERVIEW_MODAL, AGGRESSIVE_EASE_IN_OUT));
     }
 
+    abstract FloatProperty getTaskModalnessProperty();
+
     /**
      * Get property for content alpha for the recents view.
      *
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index 0569828..c715c93 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -24,9 +24,7 @@
 import android.content.res.Resources;
 import android.util.Log;
 
-import com.android.launcher3.Launcher;
 import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.quickstep.views.RecentsView;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -130,14 +128,10 @@
         }
     }
 
-    public static boolean removeShelfFromOverview(Launcher launcher) {
-        // The shelf is core to the two-button mode model, so we need to continue supporting it
-        // when in portrait.
-        if (getMode(launcher) != Mode.TWO_BUTTONS) {
-            return true;
-        }
-        RecentsView recentsView = launcher.getOverviewPanel();
-        return !recentsView.getPagedOrientationHandler().isLayoutNaturalToLauncher();
+    /** @return Whether we can remove the shelf from overview. */
+    public static boolean removeShelfFromOverview(Context context) {
+        // The shelf is core to the two-button mode model, so we need to continue supporting it.
+        return getMode(context) != Mode.TWO_BUTTONS;
     }
 
     public void dump(PrintWriter pw) {
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index a2471d8..4edf2fb 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -16,8 +16,7 @@
 package com.android.quickstep.util;
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
-import static com.android.quickstep.SysUINavigationMode.getMode;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -61,7 +60,7 @@
         } else {
             Resources res = context.getResources();
 
-            if (showOverviewActions(context)) {
+            if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
                 //TODO: this needs to account for the swipe gesture height and accessibility
                 // UI when shown.
                 extraSpace = res.getDimensionPixelSize(R.dimen.overview_actions_height);
@@ -77,7 +76,7 @@
 
     public static void calculateFallbackTaskSize(Context context, DeviceProfile dp, Rect outRect) {
         float extraSpace;
-        if (showOverviewActions(context)) {
+        if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
             extraSpace = context.getResources()
                     .getDimensionPixelSize(R.dimen.overview_actions_height);
         } else {
@@ -92,7 +91,7 @@
         float taskWidth, taskHeight, paddingHorz;
         Resources res = context.getResources();
         Rect insets = dp.getInsets();
-        final boolean showLargeTaskSize = showOverviewActions(context);
+        final boolean overviewActionsEnabled = ENABLE_OVERVIEW_ACTIONS.get();
 
         if (dp.isMultiWindowMode) {
             if (multiWindowStrategy == MULTI_WINDOW_STRATEGY_HALF_SCREEN) {
@@ -122,7 +121,7 @@
             final int paddingResId;
             if (dp.isVerticalBarLayout()) {
                 paddingResId = R.dimen.landscape_task_card_horz_space;
-            } else if (showLargeTaskSize) {
+            } else if (overviewActionsEnabled && removeShelfFromOverview(context)) {
                 paddingResId = R.dimen.portrait_task_card_horz_space_big_overview;
             } else {
                 paddingResId = R.dimen.portrait_task_card_horz_space;
@@ -131,7 +130,7 @@
         }
 
         float topIconMargin = res.getDimension(R.dimen.task_thumbnail_top_margin);
-        float paddingVert = showLargeTaskSize
+        float paddingVert = overviewActionsEnabled && removeShelfFromOverview(context)
                 ? 0 : res.getDimension(R.dimen.task_card_vert_space);
 
         // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
@@ -157,7 +156,7 @@
 
     public static int getShelfTrackingDistance(Context context, DeviceProfile dp) {
         // Track the bottom of the window.
-        if (showOverviewActions(context)) {
+        if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
             Rect taskSize = new Rect();
             calculateLauncherTaskSize(context, dp, taskSize);
             return (dp.heightPx - taskSize.height()) / 2;
@@ -181,8 +180,4 @@
             return srcHeight / targetHeight;
         }
     }
-
-    private static boolean showOverviewActions(Context context) {
-        return ENABLE_OVERVIEW_ACTIONS.get() && getMode(context) != TWO_BUTTONS;
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index c6384d3..74daeca 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -25,7 +25,6 @@
 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
-import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.content.ContentResolver;
@@ -50,7 +49,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.quickstep.SysUINavigationMode;
 
 import java.lang.annotation.Retention;
 import java.util.function.IntConsumer;
@@ -156,9 +154,7 @@
         if (isFixedRotationTransformEnabled(context)) {
             mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_FLAG;
         }
-        // TODO(b/154665738): Determine if we need animation for 2 button mode or not
-        if (mOrientationListener.canDetectOrientation()
-                && SysUINavigationMode.getMode(context) != TWO_BUTTONS) {
+        if (mOrientationListener.canDetectOrientation()) {
             mFlags |= FLAG_ROTATION_WATCHER_SUPPORTED;
         }
     }
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index af77c62..f5498c9 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -165,7 +165,7 @@
             if ((OVERVIEW.getVisibleElements(mLauncher) & ALL_APPS_HEADER_EXTRA) == 0) {
                 mDragHandleProgress = 1;
                 if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()
-                        && SysUINavigationMode.removeShelfFromOverview(mLauncher)) {
+                        && SysUINavigationMode.removeShelfFromOverview(context)) {
                     // Fade in all apps background quickly to distinguish from swiping from nav bar.
                     mMidAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha);
                     mMidProgress = OverviewState.getDefaultVerticalProgress(mLauncher);
diff --git a/src/com/android/launcher3/InsettableFrameLayout.java b/src/com/android/launcher3/InsettableFrameLayout.java
index 9a66d32..faa18b8 100644
--- a/src/com/android/launcher3/InsettableFrameLayout.java
+++ b/src/com/android/launcher3/InsettableFrameLayout.java
@@ -91,9 +91,6 @@
     @Override
     public void onViewAdded(View child) {
         super.onViewAdded(child);
-        if (!isAttachedToWindow()) {
-            return;
-        }
         setFrameLayoutChildInsets(child, mInsets, new Rect());
     }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 49723d5..070a392 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 54d8f0d..e2b867e 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -36,6 +36,7 @@
 import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.OVERVIEW_MODAL_TASK_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_PEEK_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
@@ -101,7 +102,7 @@
                 }
             };
 
-    private static final LauncherState[] sAllStates = new LauncherState[8];
+    private static final LauncherState[] sAllStates = new LauncherState[9];
 
     /**
      * TODO: Create a separate class for NORMAL state.
@@ -128,6 +129,8 @@
     public static final LauncherState OVERVIEW = new OverviewState(OVERVIEW_STATE_ORDINAL);
     public static final LauncherState OVERVIEW_PEEK =
             OverviewState.newPeekState(OVERVIEW_PEEK_STATE_ORDINAL);
+    public static final LauncherState OVERVIEW_MODAL_TASK = OverviewState.newModalTaskState(
+            OVERVIEW_MODAL_TASK_STATE_ORDINAL);
     public static final LauncherState QUICK_SWITCH =
             OverviewState.newSwitchState(QUICK_SWITCH_STATE_ORDINAL);
     public static final LauncherState BACKGROUND_APP =
@@ -280,6 +283,14 @@
     }
 
     /**
+     * For this state, how modal should over view been shown. 0 modalness means all tasks drawn,
+     * 1 modalness means the current task is show on its own.
+     */
+    public float getOverviewModalness() {
+        return 0;
+    }
+
+    /**
      * The amount of blur and wallpaper zoom to apply to the background of either the app
      * or Launcher surface in this state. Should be a number between 0 and 1, inclusive.
      *
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 3c3ab6c..d95ccb4 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -340,6 +340,30 @@
     }
 
     /**
+     * Bounds parameter to the range [0, 1]
+     */
+    public static float saturate(float a) {
+        return boundToRange(a, 0, 1.0f);
+    }
+
+    /**
+     * Returns the compliment (1 - a) of the parameter.
+     */
+    public static float comp(float a) {
+        return 1 - a;
+    }
+
+    /**
+     * Returns the "probabilistic or" of a and b. (a + b - ab).
+     * Useful beyond probability, can be used to combine two unit progresses for example.
+     */
+    public static float or(float a, float b) {
+        float satA = saturate(a);
+        float satB = saturate(b);
+        return satA + satB - (satA * satB);
+    }
+
+    /**
      * Trims the string, removing all whitespace at the beginning and end of the string.
      * Non-breaking whitespaces are also removed.
      */
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
index 8dccbd3..1c49867 100644
--- a/src/com/android/launcher3/states/StateAnimationConfig.java
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -69,6 +69,7 @@
             ANIM_ALL_APPS_FADE,
             ANIM_OVERVIEW_SCRIM_FADE,
             ANIM_ALL_APPS_HEADER_FADE,
+            ANIM_OVERVIEW_MODAL
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AnimType {}
@@ -85,8 +86,9 @@
     public static final int ANIM_ALL_APPS_FADE = 10;
     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 ANIM_OVERVIEW_MODAL = 13;
 
-    private static final int ANIM_TYPES_COUNT = 13;
+    private static final int ANIM_TYPES_COUNT = 14;
 
     private final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
 
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index a5a06b4..fba6269 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -28,10 +28,11 @@
     public static final int SPRING_LOADED_STATE_ORDINAL = 1;
     public static final int OVERVIEW_STATE_ORDINAL = 2;
     public static final int OVERVIEW_PEEK_STATE_ORDINAL = 3;
-    public static final int QUICK_SWITCH_STATE_ORDINAL = 4;
-    public static final int ALL_APPS_STATE_ORDINAL = 5;
-    public static final int BACKGROUND_APP_STATE_ORDINAL = 6;
-    public static final int HINT_STATE_ORDINAL = 7;
+    public static final int OVERVIEW_MODAL_TASK_STATE_ORDINAL = 4;
+    public static final int QUICK_SWITCH_STATE_ORDINAL = 5;
+    public static final int ALL_APPS_STATE_ORDINAL = 6;
+    public static final int BACKGROUND_APP_STATE_ORDINAL = 7;
+    public static final int HINT_STATE_ORDINAL = 8;
     public static final String TAPL_EVENTS_TAG = "TaplEvents";
     public static final String SEQUENCE_MAIN = "Main";
     public static final String SEQUENCE_TIS = "TIS";
@@ -47,6 +48,8 @@
                 return "Overview";
             case OVERVIEW_PEEK_STATE_ORDINAL:
                 return "OverviewPeek";
+            case OVERVIEW_MODAL_TASK_STATE_ORDINAL:
+                return "OverviewModalState";
             case QUICK_SWITCH_STATE_ORDINAL:
                 return "QuickSwitch";
             case ALL_APPS_STATE_ORDINAL:
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
index e20b2ca..507ff59 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -44,4 +44,11 @@
     public static OverviewState newSwitchState(int id) {
         return new OverviewState(id);
     }
+
+    /**
+     *  New Overview substate that represents the overview in modal mode (one task shown on its own)
+     */
+    public static OverviewState newModalTaskState(int id) {
+        return new OverviewState(id);
+    }
 }