Merge "[CD Taskbar] Refactor TaskbarManager - Add Debugging Support for Stability." into main
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
index cca8bf8..04e1905 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
@@ -27,17 +27,16 @@
 import com.android.launcher3.anim.AnimatorListeners.forSuccessCallback
 import com.android.launcher3.anim.PendingAnimation
 import com.android.launcher3.anim.PropertySetter
-import com.android.launcher3.logging.StatsLogManager.LauncherEvent
 import com.android.launcher3.statemanager.StateManager.StateHandler
 import com.android.launcher3.states.StateAnimationConfig
 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE
 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE
 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL
 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE
-import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE
 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X
 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y
 import com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW
+import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
 import com.android.quickstep.util.AnimUtils
 import com.android.quickstep.views.ClearAllButton
 import com.android.quickstep.views.RecentsView
@@ -226,8 +225,8 @@
         builder: PendingAnimation,
         animate: Boolean,
     ) {
-        val goingToOverviewFromWorkspaceContextual = toState == LauncherState.OVERVIEW &&
-                launcher.isSplitSelectionActive
+        val goingToOverviewFromWorkspaceContextual =
+            toState == LauncherState.OVERVIEW && launcher.isSplitSelectionActive
         if (
             toState != LauncherState.OVERVIEW_SPLIT_SELECT &&
                 !goingToOverviewFromWorkspaceContextual
@@ -302,6 +301,14 @@
             overviewButtonAlpha,
             config.getInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, LINEAR),
         )
+        recentsView.addDeskButton?.let {
+            propertySetter.setFloat(
+                it.visibilityAlphaProperty,
+                MULTI_PROPERTY_VALUE,
+                if (state.areElementsVisible(launcher, LauncherState.ADD_DESK_BUTTON)) 1f else 0f,
+                LINEAR,
+            )
+        }
     }
 
     private fun getOverviewInterpolator(fromState: LauncherState, toState: LauncherState) =
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index ca388c6..b1196af 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -77,7 +77,8 @@
         return super.getVisibleElements(launcher)
                 & ~OVERVIEW_ACTIONS
                 & ~CLEAR_ALL_BUTTON
-                & ~VERTICAL_SWIPE_INDICATOR;
+                & ~VERTICAL_SWIPE_INDICATOR
+                & ~ADD_DESK_BUTTON;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 80fc5fa..0c0b4fd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -45,7 +45,7 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        return OVERVIEW_ACTIONS | CLEAR_ALL_BUTTON;
+        return OVERVIEW_ACTIONS;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 15216fe..5fdedcc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -110,7 +110,7 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        int elements = CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
+        int elements = CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS | ADD_DESK_BUTTON;
         DeviceProfile dp = launcher.getDeviceProfile();
         boolean showFloatingSearch;
         if (dp.isPhone) {
@@ -124,7 +124,7 @@
             elements |= FLOATING_SEARCH_BAR;
         }
         if (launcher.isSplitSelectionActive()) {
-            elements &= ~CLEAR_ALL_BUTTON;
+            elements &= ~CLEAR_ALL_BUTTON & ~ADD_DESK_BUTTON;
         }
         return elements;
     }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 56dd696..537092f 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -26,6 +26,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
+import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
 import static com.android.quickstep.fallback.RecentsState.OVERVIEW_SPLIT_SELECT;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.DESKTOP_CAROUSEL_DETACH_PROGRESS;
@@ -99,6 +100,11 @@
         float clearAllButtonAlpha = state.hasClearAllButton() ? 1 : 0;
         setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
                 clearAllButtonAlpha, LINEAR);
+        if (mRecentsView.getAddDeskButton() != null) {
+            float addDeskButtonAlpha = state.hasAddDeskButton() ? 1 : 0;
+            setter.setFloat(mRecentsView.getAddDeskButton().getVisibilityAlphaProperty(),
+                    MULTI_PROPERTY_VALUE, addDeskButtonAlpha, LINEAR);
+        }
         float overviewButtonAlpha = state.hasOverviewActions() ? 1 : 0;
         setter.setFloat(mRecentsViewContainer.getActionsView().getVisibilityAlpha(),
                 AnimatedFloat.VALUE, overviewButtonAlpha, LINEAR);
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index f27b60c..f722c5d 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -45,14 +45,16 @@
     private static final int FLAG_RECENTS_VIEW_VISIBLE = BaseState.getFlag(7);
     private static final int FLAG_TASK_THUMBNAIL_SPLASH = BaseState.getFlag(8);
     private static final int FLAG_DETACH_DESKTOP_CAROUSEL = BaseState.getFlag(9);
+    private static final int FLAG_ADD_DESK_BUTTON = BaseState.getFlag(10);
 
     private static final RecentsState[] sAllStates = new RecentsState[6];
 
     public static final RecentsState DEFAULT = new RecentsState(0,
             FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID
-                    | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE);
+                    | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE
+                    | FLAG_ADD_DESK_BUTTON);
     public static final RecentsState MODAL_TASK = new ModalState(1,
-            FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
+            FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
                     | FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE);
     public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
             FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN
@@ -122,6 +124,13 @@
     }
 
     /**
+     * For this state, whether add desk button should be shown.
+     */
+    public boolean hasAddDeskButton() {
+        return hasFlag(FLAG_ADD_DESK_BUTTON);
+    }
+
+    /**
      * For this state, whether overview actions should be shown.
      */
     public boolean hasOverviewActions() {
diff --git a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
index 9943770..244c3b2 100644
--- a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
+++ b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
@@ -20,10 +20,12 @@
 import android.graphics.Canvas
 import android.graphics.Rect
 import android.util.AttributeSet
+import android.view.View
 import android.widget.ImageButton
 import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
 import com.android.launcher3.R
 import com.android.launcher3.util.MultiPropertyFactory
+import com.android.launcher3.util.MultiValueAlpha
 import com.android.quickstep.util.BorderAnimator
 import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator
 
@@ -34,6 +36,22 @@
 class AddDesktopButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
     ImageButton(context, attrs) {
 
+    private enum class Alpha {
+        CONTENT,
+        VISIBILITY,
+    }
+
+    private val addDeskButtonAlpha = MultiValueAlpha(this, Alpha.entries.size)
+
+    var contentAlpha
+        set(value) {
+            addDeskButtonAlpha.get(Alpha.CONTENT.ordinal).value = value
+        }
+        get() = addDeskButtonAlpha.get(Alpha.CONTENT.ordinal).value
+
+    val visibilityAlphaProperty: MultiPropertyFactory<View>.MultiProperty
+        get() = addDeskButtonAlpha.get(Alpha.VISIBILITY.ordinal)
+
     private enum class TranslationX {
         GRID,
         OFFSET,
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index d17dfb8..6b91df1 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -4762,9 +4762,8 @@
         }
         mClearAllButton.setContentAlpha(mContentAlpha);
 
-        // TODO(b/389209338): Handle the visibility of the `mAddDesktopButton`.
         if (mAddDesktopButton != null) {
-            mAddDesktopButton.setAlpha(mContentAlpha);
+            mAddDesktopButton.setContentAlpha(mContentAlpha);
         }
         int alphaInt = Math.round(alpha * 255);
         mEmptyMessagePaint.setAlpha(alphaInt);
@@ -6329,6 +6328,11 @@
         return mClearAllButton;
     }
 
+    @Nullable
+    public AddDesktopButton getAddDeskButton() {
+        return mAddDesktopButton;
+    }
+
     /**
      * @return How many pixels the running task is offset on the currently laid out dominant axis.
      */
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
deleted file mode 100644
index 0b3eb75..0000000
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ /dev/null
@@ -1,429 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.views;
-
-import static com.android.app.animation.Interpolators.EMPHASIZED;
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
-import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.quickstep.views.TaskThumbnailViewDeprecated.DIM_ALPHA;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.RectShape;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.app.animation.Interpolators;
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.TaskOverlayFactory;
-import com.android.quickstep.TaskUtils;
-import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.quickstep.util.TaskCornerRadius;
-
-/**
- * Contains options for a recent task when long-pressing its icon.
- */
-public class TaskMenuView extends AbstractFloatingView {
-
-    private static final Rect sTempRect = new Rect();
-
-    private static final int REVEAL_OPEN_DURATION = enableOverviewIconMenu() ? 417 : 150;
-    private static final int REVEAL_CLOSE_DURATION = enableOverviewIconMenu() ? 333 : 100;
-
-    private RecentsViewContainer mContainer;
-    private TextView mTaskName;
-    @Nullable
-    private AnimatorSet mOpenCloseAnimator;
-    @Nullable
-    private ValueAnimator mRevealAnimator;
-    @Nullable private Runnable mOnClosingStartCallback;
-    private TaskView mTaskView;
-    private TaskContainer mTaskContainer;
-    private LinearLayout mOptionLayout;
-    private float mMenuTranslationYBeforeOpen;
-    private float mMenuTranslationXBeforeOpen;
-
-    public TaskMenuView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public TaskMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-
-        mContainer = RecentsViewContainer.containerFromContext(context);
-        setClipToOutline(true);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mTaskName = findViewById(R.id.task_name);
-        mOptionLayout = findViewById(R.id.menu_option_layout);
-    }
-
-    @Override
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            BaseDragLayer dl = mContainer.getDragLayer();
-            if (!dl.isEventOverView(this, ev)) {
-                // TODO: log this once we have a new container type for it?
-                close(true);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    protected void handleClose(boolean animate) {
-        animateClose();
-    }
-
-    @Override
-    protected boolean isOfType(int type) {
-        return (type & TYPE_TASK_MENU) != 0;
-    }
-
-    @Override
-    public ViewOutlineProvider getOutlineProvider() {
-        return new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(),
-                        TaskCornerRadius.get(view.getContext()));
-            }
-        };
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        if (!(enableOverviewIconMenu()
-                && ((RecentsView) mContainer.getOverviewPanel()).isOnGridBottomRow(mTaskView))) {
-            // TODO(b/326952853): Cap menu height for grid bottom row in a way that doesn't break
-            // additionalTranslationY.
-            int maxMenuHeight = calculateMaxHeight();
-            if (MeasureSpec.getSize(heightMeasureSpec) > maxMenuHeight) {
-                heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST);
-            }
-        }
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    public void onRotationChanged() {
-        if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
-            mOpenCloseAnimator.end();
-        }
-        if (mIsOpen) {
-            mOptionLayout.removeAllViews();
-            if (enableOverviewIconMenu() || !populateAndLayoutMenu()) {
-                close(false);
-            }
-        }
-    }
-
-    /**
-     * Show a task menu for the given taskContainer.
-     */
-    public static boolean showForTask(TaskContainer taskContainer,
-            @Nullable Runnable onClosingStartCallback) {
-        RecentsViewContainer container = RecentsViewContainer.containerFromContext(
-                taskContainer.getTaskView().getContext());
-        final TaskMenuView taskMenuView = (TaskMenuView) container.getLayoutInflater().inflate(
-                        R.layout.task_menu, container.getDragLayer(), false);
-        taskMenuView.setOnClosingStartCallback(onClosingStartCallback);
-        return taskMenuView.populateAndShowForTask(taskContainer);
-    }
-
-    /**
-     * Show a task menu for the given taskContainer.
-     */
-    public static boolean showForTask(TaskContainer taskContainer) {
-        return showForTask(taskContainer, null);
-    }
-
-    private boolean populateAndShowForTask(TaskContainer taskContainer) {
-        if (isAttachedToWindow()) {
-            return false;
-        }
-        mContainer.getDragLayer().addView(this);
-        mTaskView = taskContainer.getTaskView();
-        mTaskContainer = taskContainer;
-        if (!populateAndLayoutMenu()) {
-            return false;
-        }
-        post(this::animateOpen);
-        return true;
-    }
-
-    /** @return true if successfully able to populate task view menu, false otherwise */
-    private boolean populateAndLayoutMenu() {
-        addMenuOptions(mTaskContainer);
-        orientAroundTaskView(mTaskContainer);
-        return true;
-    }
-
-    private void addMenuOptions(TaskContainer taskContainer) {
-        if (enableOverviewIconMenu()) {
-            removeView(mTaskName);
-        } else {
-            mTaskName.setText(TaskUtils.getTitle(getContext(), taskContainer.getTask()));
-            mTaskName.setOnClickListener(v -> close(true));
-        }
-        TaskOverlayFactory.getEnabledShortcuts(mTaskView, taskContainer)
-                .forEach(this::addMenuOption);
-    }
-
-    private void addMenuOption(SystemShortcut menuOption) {
-        LinearLayout menuOptionView = (LinearLayout) mContainer.getLayoutInflater().inflate(
-                R.layout.task_view_menu_option, this, false);
-        if (enableOverviewIconMenu()) {
-            ((GradientDrawable) menuOptionView.getBackground()).setCornerRadius(0);
-        }
-        menuOption.setIconAndLabelFor(
-                menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
-        LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
-        mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp,
-                menuOptionView, mContainer.getDeviceProfile());
-        // Set an onClick listener on each menu option. The onClick method is responsible for
-        // ending LiveTile mode on the thumbnail if needed.
-        menuOptionView.setOnClickListener(menuOption::onClick);
-        mOptionLayout.addView(menuOptionView);
-    }
-
-    private void orientAroundTaskView(TaskContainer taskContainer) {
-        RecentsView recentsView = mContainer.getOverviewPanel();
-        RecentsPagedOrientationHandler orientationHandler =
-                recentsView.getPagedOrientationHandler();
-        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-
-        // Get Position
-        DeviceProfile deviceProfile = mContainer.getDeviceProfile();
-        mContainer.getDragLayer().getDescendantRectRelativeToSelf(
-                enableOverviewIconMenu()
-                        ? getIconView().findViewById(R.id.icon_view_menu_anchor)
-                        : taskContainer.getSnapshotView(),
-                sTempRect);
-        Rect insets = mContainer.getDragLayer().getInsets();
-        BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
-        params.width = orientationHandler.getTaskMenuWidth(
-                taskContainer.getSnapshotView(), deviceProfile,
-                taskContainer.getStagePosition());
-        // Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start
-        params.gravity = Gravity.LEFT;
-        setLayoutParams(params);
-        setScaleX(mTaskView.getScaleX());
-        setScaleY(mTaskView.getScaleY());
-
-        // Set divider spacing
-        ShapeDrawable divider = new ShapeDrawable(new RectShape());
-        divider.getPaint().setColor(getResources().getColor(android.R.color.transparent));
-        int dividerSpacing = (int) getResources().getDimension(R.dimen.task_menu_spacing);
-        mOptionLayout.setShowDividers(
-                enableOverviewIconMenu() ? SHOW_DIVIDER_NONE : SHOW_DIVIDER_MIDDLE);
-
-        orientationHandler.setTaskOptionsMenuLayoutOrientation(
-                deviceProfile, mOptionLayout, dividerSpacing, divider);
-        float thumbnailAlignedX = sTempRect.left - insets.left;
-        float thumbnailAlignedY = sTempRect.top - insets.top;
-
-        // Changing pivot to make computations easier
-        // NOTE: Changing the pivots means the rotated view gets rotated about the new pivots set,
-        // which would render the X and Y position set here incorrect
-        setPivotX(0);
-        setPivotY(0);
-        setRotation(orientationHandler.getDegreesRotated());
-
-        if (enableOverviewIconMenu()) {
-            setTranslationX(thumbnailAlignedX);
-            setTranslationY(thumbnailAlignedY);
-        } else {
-            // Margin that insets the menuView inside the taskView
-            float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin);
-            setTranslationX(orientationHandler.getTaskMenuX(thumbnailAlignedX,
-                    mTaskContainer.getSnapshotView(), deviceProfile, taskInsetMargin,
-                    getIconView()));
-            setTranslationY(orientationHandler.getTaskMenuY(
-                    thumbnailAlignedY, mTaskContainer.getSnapshotView(),
-                    mTaskContainer.getStagePosition(), this, taskInsetMargin,
-                    getIconView()));
-        }
-    }
-
-    private void animateOpen() {
-        mMenuTranslationYBeforeOpen = getTranslationY();
-        mMenuTranslationXBeforeOpen = getTranslationX();
-        animateOpenOrClosed(false);
-        mIsOpen = true;
-    }
-
-    private View getIconView() {
-        return mTaskContainer.getIconView().asView();
-    }
-
-    private void animateClose() {
-        animateOpenOrClosed(true);
-    }
-
-    private void animateOpenOrClosed(boolean closing) {
-        if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
-            mOpenCloseAnimator.cancel();
-        }
-        mOpenCloseAnimator = new AnimatorSet();
-        // If we're opening, we just start from the beginning as a new `TaskMenuView` is created
-        // each time we do the open animation so there will never be a partial value here.
-        float revealAnimationStartProgress = 0f;
-        if (closing && mRevealAnimator != null) {
-            revealAnimationStartProgress = 1f - mRevealAnimator.getAnimatedFraction();
-        }
-        mRevealAnimator = createOpenCloseOutlineProvider()
-                .createRevealAnimator(this, closing, revealAnimationStartProgress);
-        mRevealAnimator.setInterpolator(enableOverviewIconMenu() ? Interpolators.EMPHASIZED
-                : Interpolators.DECELERATE);
-        AnimatorSet.Builder openCloseAnimatorBuilder = mOpenCloseAnimator.play(mRevealAnimator);
-        if (enableOverviewIconMenu()) {
-            IconAppChipView iconAppChip = (IconAppChipView) mTaskContainer.getIconView().asView();
-
-            float additionalTranslationY = 0;
-            if (((RecentsView) mContainer.getOverviewPanel()).isOnGridBottomRow(mTaskView)) {
-                // Animate menu up for enough room to display full menu when task on bottom row.
-                float menuBottom = getHeight() + mMenuTranslationYBeforeOpen;
-                float taskBottom = mTaskView.getHeight() + mTaskView.getPersistentTranslationY();
-                float taskbarTop = mContainer.getDeviceProfile().heightPx
-                        - mContainer.getDeviceProfile().getOverviewActionsClaimedSpaceBelow();
-                float midpoint = (taskBottom + taskbarTop) / 2f;
-                additionalTranslationY = -Math.max(menuBottom - midpoint, 0);
-            }
-            ObjectAnimator translationYAnim = ObjectAnimator.ofFloat(this, TRANSLATION_Y,
-                    closing ? mMenuTranslationYBeforeOpen
-                            : mMenuTranslationYBeforeOpen + additionalTranslationY);
-            translationYAnim.setInterpolator(EMPHASIZED);
-            openCloseAnimatorBuilder.with(translationYAnim);
-
-            ObjectAnimator menuTranslationYAnim = ObjectAnimator.ofFloat(
-                    iconAppChip.getMenuTranslationY(),
-                    MULTI_PROPERTY_VALUE, closing ? 0 : additionalTranslationY);
-            menuTranslationYAnim.setInterpolator(EMPHASIZED);
-            openCloseAnimatorBuilder.with(menuTranslationYAnim);
-
-            float additionalTranslationX = 0;
-            if (mContainer.getDeviceProfile().isLandscape
-                    && mTaskContainer.getStagePosition() == STAGE_POSITION_BOTTOM_OR_RIGHT) {
-                // Animate menu and icon when split task would display off the side of the screen.
-                additionalTranslationX = Math.max(
-                        getTranslationX() + getWidth() - (mContainer.getDeviceProfile().widthPx
-                                - getResources().getDimensionPixelSize(
-                                R.dimen.task_menu_edge_padding) * 2), 0);
-            }
-
-            ObjectAnimator translationXAnim = ObjectAnimator.ofFloat(this, TRANSLATION_X,
-                    closing ? mMenuTranslationXBeforeOpen
-                            : mMenuTranslationXBeforeOpen - additionalTranslationX);
-            translationXAnim.setInterpolator(EMPHASIZED);
-            openCloseAnimatorBuilder.with(translationXAnim);
-
-            ObjectAnimator menuTranslationXAnim = ObjectAnimator.ofFloat(
-                    iconAppChip.getMenuTranslationX(),
-                    MULTI_PROPERTY_VALUE, closing ? 0 : -additionalTranslationX);
-            menuTranslationXAnim.setInterpolator(EMPHASIZED);
-            openCloseAnimatorBuilder.with(menuTranslationXAnim);
-        }
-        openCloseAnimatorBuilder.with(ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1));
-        if (enableRefactorTaskThumbnail()) {
-            mRevealAnimator.addUpdateListener(animation -> {
-                float animatedFraction = animation.getAnimatedFraction();
-                float openProgress = closing ? (1 - animatedFraction) : animatedFraction;
-                mTaskContainer.updateMenuOpenProgress(openProgress);
-            });
-        } else {
-            openCloseAnimatorBuilder.with(ObjectAnimator.ofFloat(
-                    mTaskContainer.getThumbnailViewDeprecated(), DIM_ALPHA,
-                    closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA));
-        }
-        mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                setVisibility(VISIBLE);
-                if (closing && mOnClosingStartCallback != null) {
-                    mOnClosingStartCallback.run();
-                }
-            }
-
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                if (closing) {
-                    closeComplete();
-                }
-            }
-        });
-        mOpenCloseAnimator.setDuration(closing ? REVEAL_CLOSE_DURATION: REVEAL_OPEN_DURATION);
-        mOpenCloseAnimator.start();
-    }
-
-    private void closeComplete() {
-        mIsOpen = false;
-        mContainer.getDragLayer().removeView(this);
-        mRevealAnimator = null;
-    }
-
-    private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
-        float radius = TaskCornerRadius.get(mContext);
-        Rect fromRect = new Rect(
-                enableOverviewIconMenu() && isLayoutRtl() ? getWidth() : 0,
-                0,
-                enableOverviewIconMenu() && !isLayoutRtl() ? 0 : getWidth(),
-                0);
-        Rect toRect = new Rect(0, 0, getWidth(), getHeight());
-        return new RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect);
-    }
-
-    /**
-     * Calculates max height based on how much space we have available.
-     * If not enough space then the view will scroll. The maximum menu size will sit inside the task
-     * with a margin on the top and bottom.
-     */
-    private int calculateMaxHeight() {
-        float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin);
-        return mTaskView.getPagedOrientationHandler().getTaskMenuHeight(taskInsetMargin,
-                mContainer.getDeviceProfile(), getTranslationX(), getTranslationY());
-    }
-
-    private void setOnClosingStartCallback(Runnable onClosingStartCallback) {
-        mOnClosingStartCallback = onClosingStartCallback;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.kt b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
new file mode 100644
index 0000000..95336cf
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
@@ -0,0 +1,455 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Outline
+import android.graphics.Rect
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RectShape
+import android.util.AttributeSet
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewOutlineProvider
+import android.widget.LinearLayout
+import android.widget.TextView
+import com.android.app.animation.Interpolators
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.Flags.enableRefactorTaskThumbnail
+import com.android.launcher3.R
+import com.android.launcher3.anim.AnimationSuccessListener
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider
+import com.android.launcher3.popup.SystemShortcut
+import com.android.launcher3.util.MultiPropertyFactory
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.views.BaseDragLayer
+import com.android.quickstep.TaskOverlayFactory
+import com.android.quickstep.TaskUtils
+import com.android.quickstep.util.TaskCornerRadius
+import java.util.function.Consumer
+import kotlin.math.max
+
+/** Contains options for a recent task when long-pressing its icon. */
+class TaskMenuView
+@JvmOverloads
+constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int = 0) :
+    AbstractFloatingView(context, attrs, defStyleAttr) {
+    private val recentsViewContainer: RecentsViewContainer =
+        RecentsViewContainer.containerFromContext(context)
+    private val tempRect = Rect()
+    private val taskName: TextView by lazy { findViewById(R.id.task_name) }
+    private val optionLayout: LinearLayout by lazy { findViewById(R.id.menu_option_layout) }
+    private var openCloseAnimator: AnimatorSet? = null
+    private var revealAnimator: ValueAnimator? = null
+    private var onClosingStartCallback: Runnable? = null
+    private lateinit var taskView: TaskView
+    private lateinit var taskContainer: TaskContainer
+    private var menuTranslationXBeforeOpen = 0f
+    private var menuTranslationYBeforeOpen = 0f
+
+    init {
+        clipToOutline = true
+    }
+
+    override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
+        if (ev.action == MotionEvent.ACTION_DOWN) {
+            if (!recentsViewContainer.dragLayer.isEventOverView(this, ev)) {
+                // TODO: log this once we have a new container type for it?
+                close(true)
+                return true
+            }
+        }
+        return false
+    }
+
+    override fun handleClose(animate: Boolean) {
+        animateClose()
+    }
+
+    override fun isOfType(type: Int): Boolean = (type and TYPE_TASK_MENU) != 0
+
+    override fun getOutlineProvider(): ViewOutlineProvider =
+        object : ViewOutlineProvider() {
+            override fun getOutline(view: View, outline: Outline) {
+                outline.setRoundRect(
+                    0,
+                    0,
+                    view.width,
+                    view.height,
+                    TaskCornerRadius.get(view.context),
+                )
+            }
+        }
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        var heightMeasure = heightMeasureSpec
+        if (!(enableOverviewIconMenu() && taskView.isOnGridBottomRow())) {
+            // TODO(b/326952853): Cap menu height for grid bottom row in a way that doesn't break
+            // additionalTranslationY.
+            val maxMenuHeight = calculateMaxHeight()
+            if (MeasureSpec.getSize(heightMeasure) > maxMenuHeight) {
+                heightMeasure = MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST)
+            }
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasure)
+    }
+
+    fun onRotationChanged() {
+        openCloseAnimator?.let { if (it.isRunning) it.end() }
+        if (mIsOpen) {
+            optionLayout.removeAllViews()
+            if (enableOverviewIconMenu() || !populateAndLayoutMenu()) {
+                close(false)
+            }
+        }
+    }
+
+    private fun populateAndShowForTask(taskContainer: TaskContainer): Boolean {
+        if (isAttachedToWindow) return false
+        recentsViewContainer.dragLayer.addView(this)
+        taskView = taskContainer.taskView
+        this.taskContainer = taskContainer
+        if (!populateAndLayoutMenu()) return false
+        post { this.animateOpen() }
+        return true
+    }
+
+    /** @return true if successfully able to populate task view menu, false otherwise */
+    private fun populateAndLayoutMenu(): Boolean {
+        addMenuOptions(taskContainer)
+        orientAroundTaskView(taskContainer)
+        return true
+    }
+
+    private fun addMenuOptions(taskContainer: TaskContainer) {
+        if (enableOverviewIconMenu()) {
+            removeView(taskName)
+        } else {
+            taskName.text = TaskUtils.getTitle(context, taskContainer.task)
+            taskName.setOnClickListener { close(true) }
+        }
+        TaskOverlayFactory.getEnabledShortcuts(taskView, taskContainer)
+            .forEach(Consumer { menuOption: SystemShortcut<*> -> this.addMenuOption(menuOption) })
+    }
+
+    private fun addMenuOption(menuOption: SystemShortcut<*>) {
+        val menuOptionView =
+            recentsViewContainer.layoutInflater.inflate(R.layout.task_view_menu_option, this, false)
+                as LinearLayout
+        if (enableOverviewIconMenu()) {
+            (menuOptionView.background as GradientDrawable).cornerRadius = 0f
+        }
+        menuOption.setIconAndLabelFor(
+            menuOptionView.findViewById(R.id.icon),
+            menuOptionView.findViewById(R.id.text),
+        )
+        val lp = menuOptionView.layoutParams as LayoutParams
+        taskView.pagedOrientationHandler.setLayoutParamsForTaskMenuOptionItem(
+            lp,
+            menuOptionView,
+            recentsViewContainer.deviceProfile,
+        )
+        // Set an onClick listener on each menu option. The onClick method is responsible for
+        // ending LiveTile mode on the thumbnail if needed.
+        menuOptionView.setOnClickListener { v: View? -> menuOption.onClick(v) }
+        optionLayout.addView(menuOptionView)
+    }
+
+    private fun orientAroundTaskView(taskContainer: TaskContainer) {
+        val recentsView = recentsViewContainer.getOverviewPanel<RecentsView<*, *>>()
+        val orientationHandler = recentsView.pagedOrientationHandler
+        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
+
+        // Get Position
+        val deviceProfile = recentsViewContainer.deviceProfile
+        recentsViewContainer.dragLayer.getDescendantRectRelativeToSelf(
+            if (enableOverviewIconMenu()) iconView.findViewById(R.id.icon_view_menu_anchor)
+            else taskContainer.snapshotView,
+            tempRect,
+        )
+        val insets = recentsViewContainer.dragLayer.getInsets()
+        val params = layoutParams as BaseDragLayer.LayoutParams
+        params.width =
+            orientationHandler.getTaskMenuWidth(
+                taskContainer.snapshotView,
+                deviceProfile,
+                taskContainer.stagePosition,
+            )
+        // Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start
+        params.gravity = Gravity.START
+        layoutParams = params
+        scaleX = taskView.scaleX
+        scaleY = taskView.scaleY
+
+        // Set divider spacing
+        val divider = ShapeDrawable(RectShape())
+        divider.paint.color = resources.getColor(android.R.color.transparent)
+        val dividerSpacing = resources.getDimension(R.dimen.task_menu_spacing).toInt()
+        optionLayout.showDividers =
+            if (enableOverviewIconMenu()) SHOW_DIVIDER_NONE else SHOW_DIVIDER_MIDDLE
+
+        orientationHandler.setTaskOptionsMenuLayoutOrientation(
+            deviceProfile,
+            optionLayout,
+            dividerSpacing,
+            divider,
+        )
+        val thumbnailAlignedX = (tempRect.left - insets.left).toFloat()
+        val thumbnailAlignedY = (tempRect.top - insets.top).toFloat()
+
+        // Changing pivot to make computations easier
+        // NOTE: Changing the pivots means the rotated view gets rotated about the new pivots set,
+        // which would render the X and Y position set here incorrect
+        pivotX = 0f
+        pivotY = 0f
+        rotation = orientationHandler.degreesRotated
+
+        if (enableOverviewIconMenu()) {
+            translationX = thumbnailAlignedX
+            translationY = thumbnailAlignedY
+        } else {
+            // Margin that insets the menuView inside the taskView
+            val taskInsetMargin = resources.getDimension(R.dimen.task_card_margin)
+            translationX =
+                orientationHandler.getTaskMenuX(
+                    thumbnailAlignedX,
+                    this.taskContainer.snapshotView,
+                    deviceProfile,
+                    taskInsetMargin,
+                    iconView,
+                )
+            translationY =
+                orientationHandler.getTaskMenuY(
+                    thumbnailAlignedY,
+                    this.taskContainer.snapshotView,
+                    this.taskContainer.stagePosition,
+                    this,
+                    taskInsetMargin,
+                    iconView,
+                )
+        }
+    }
+
+    private fun animateOpen() {
+        menuTranslationYBeforeOpen = translationY
+        menuTranslationXBeforeOpen = translationX
+        animateOpenOrClosed(false)
+        mIsOpen = true
+    }
+
+    private val iconView: View
+        get() = taskContainer.iconView.asView()
+
+    private fun animateClose() {
+        animateOpenOrClosed(true)
+    }
+
+    private fun animateOpenOrClosed(closing: Boolean) {
+        openCloseAnimator?.let { if (it.isRunning) it.cancel() }
+        openCloseAnimator = AnimatorSet()
+        // If we're opening, we just start from the beginning as a new `TaskMenuView` is created
+        // each time we do the open animation so there will never be a partial value here.
+        var revealAnimationStartProgress = 0f
+        if (closing && revealAnimator != null) {
+            revealAnimationStartProgress = 1f - revealAnimator!!.animatedFraction
+        }
+        revealAnimator =
+            createOpenCloseOutlineProvider()
+                .createRevealAnimator(this, closing, revealAnimationStartProgress)
+        revealAnimator!!.interpolator =
+            if (enableOverviewIconMenu()) Interpolators.EMPHASIZED else Interpolators.DECELERATE
+        val openCloseAnimatorBuilder = openCloseAnimator!!.play(revealAnimator)
+        if (enableOverviewIconMenu()) {
+            animateOpenOrCloseAppChip(closing, openCloseAnimatorBuilder)
+        }
+        openCloseAnimatorBuilder.with(
+            ObjectAnimator.ofFloat(this, ALPHA, (if (closing) 0 else 1).toFloat())
+        )
+        if (enableRefactorTaskThumbnail()) {
+            revealAnimator?.addUpdateListener { animation: ValueAnimator ->
+                val animatedFraction = animation.animatedFraction
+                val openProgress = if (closing) (1 - animatedFraction) else animatedFraction
+                taskContainer.updateMenuOpenProgress(openProgress)
+            }
+        } else {
+            openCloseAnimatorBuilder.with(
+                ObjectAnimator.ofFloat(
+                    taskContainer.thumbnailViewDeprecated,
+                    TaskThumbnailViewDeprecated.DIM_ALPHA,
+                    if (closing) 0f else TaskView.MAX_PAGE_SCRIM_ALPHA,
+                )
+            )
+        }
+        openCloseAnimator!!.addListener(
+            object : AnimationSuccessListener() {
+                override fun onAnimationStart(animation: Animator) {
+                    visibility = VISIBLE
+                    if (closing) onClosingStartCallback?.run()
+                }
+
+                override fun onAnimationSuccess(animator: Animator) {
+                    if (closing) closeComplete()
+                }
+            }
+        )
+        val animationDuration = if (closing) REVEAL_CLOSE_DURATION else REVEAL_OPEN_DURATION
+        openCloseAnimator!!.setDuration(animationDuration)
+        openCloseAnimator!!.start()
+    }
+
+    private fun TaskView.isOnGridBottomRow(): Boolean =
+        (recentsViewContainer.getOverviewPanel<View>() as RecentsView<*, *>).isOnGridBottomRow(this)
+
+    private fun closeComplete() {
+        mIsOpen = false
+        recentsViewContainer.dragLayer.removeView(this)
+        revealAnimator = null
+    }
+
+    private fun createOpenCloseOutlineProvider(): RoundedRectRevealOutlineProvider {
+        val radius = TaskCornerRadius.get(mContext)
+        val fromRect =
+            Rect(
+                if (enableOverviewIconMenu() && isLayoutRtl) width else 0,
+                0,
+                if (enableOverviewIconMenu() && !isLayoutRtl) 0 else width,
+                0,
+            )
+        val toRect = Rect(0, 0, width, height)
+        return RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect)
+    }
+
+    /**
+     * Calculates max height based on how much space we have available. If not enough space then the
+     * view will scroll. The maximum menu size will sit inside the task with a margin on the top and
+     * bottom.
+     */
+    private fun calculateMaxHeight(): Int {
+        val taskInsetMargin = resources.getDimension(R.dimen.task_card_margin)
+        return taskView.pagedOrientationHandler.getTaskMenuHeight(
+            taskInsetMargin,
+            recentsViewContainer.deviceProfile,
+            translationX,
+            translationY,
+        )
+    }
+
+    private fun setOnClosingStartCallback(onClosingStartCallback: Runnable?) {
+        this.onClosingStartCallback = onClosingStartCallback
+    }
+
+    private fun animateOpenOrCloseAppChip(closing: Boolean, animatorBuilder: AnimatorSet.Builder) {
+        val iconAppChip = taskContainer.iconView.asView() as IconAppChipView
+
+        var additionalTranslationY = 0f
+        if (taskView.isOnGridBottomRow()) {
+            // Animate menu up for enough room to display full menu when task on bottom row.
+            val menuBottom = height + menuTranslationYBeforeOpen
+            val taskBottom = taskView.height + taskView.persistentTranslationY
+            val taskbarTop =
+                (recentsViewContainer.deviceProfile.heightPx -
+                        recentsViewContainer.deviceProfile.overviewActionsClaimedSpaceBelow)
+                    .toFloat()
+            val midpoint = (taskBottom + taskbarTop) / 2f
+            additionalTranslationY = (-max((menuBottom - midpoint).toDouble(), 0.0)).toFloat()
+        }
+        val translationYAnim =
+            ObjectAnimator.ofFloat(
+                this,
+                TRANSLATION_Y,
+                if (closing) menuTranslationYBeforeOpen
+                else menuTranslationYBeforeOpen + additionalTranslationY,
+            )
+        translationYAnim.interpolator = Interpolators.EMPHASIZED
+        animatorBuilder.with(translationYAnim)
+
+        val menuTranslationYAnim: ObjectAnimator =
+            ObjectAnimator.ofFloat(
+                iconAppChip.getMenuTranslationY(),
+                MultiPropertyFactory.MULTI_PROPERTY_VALUE,
+                if (closing) 0f else additionalTranslationY,
+            )
+        menuTranslationYAnim.interpolator = Interpolators.EMPHASIZED
+        animatorBuilder.with(menuTranslationYAnim)
+
+        var additionalTranslationX = 0f
+        if (
+            recentsViewContainer.deviceProfile.isLandscape &&
+                taskContainer.stagePosition ==
+                    SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+        ) {
+            // Animate menu and icon when split task would display off the side of the screen.
+            additionalTranslationX =
+                max(
+                        (translationX + width -
+                                (recentsViewContainer.deviceProfile.widthPx -
+                                    resources.getDimensionPixelSize(
+                                        R.dimen.task_menu_edge_padding
+                                    ) * 2))
+                            .toDouble(),
+                        0.0,
+                    )
+                    .toFloat()
+        }
+
+        val translationXAnim =
+            ObjectAnimator.ofFloat(
+                this,
+                TRANSLATION_X,
+                if (closing) menuTranslationXBeforeOpen
+                else menuTranslationXBeforeOpen - additionalTranslationX,
+            )
+        translationXAnim.interpolator = Interpolators.EMPHASIZED
+        animatorBuilder.with(translationXAnim)
+
+        val menuTranslationXAnim: ObjectAnimator =
+            ObjectAnimator.ofFloat(
+                iconAppChip.getMenuTranslationX(),
+                MultiPropertyFactory.MULTI_PROPERTY_VALUE,
+                if (closing) 0f else -additionalTranslationX,
+            )
+        menuTranslationXAnim.interpolator = Interpolators.EMPHASIZED
+        animatorBuilder.with(menuTranslationXAnim)
+    }
+
+    companion object {
+        private val REVEAL_OPEN_DURATION = if (enableOverviewIconMenu()) 417L else 150L
+        private val REVEAL_CLOSE_DURATION = if (enableOverviewIconMenu()) 333L else 100L
+
+        /** Show a task menu for the given taskContainer. */
+        /** Show a task menu for the given taskContainer. */
+        @JvmOverloads
+        fun showForTask(
+            taskContainer: TaskContainer,
+            onClosingStartCallback: Runnable? = null,
+        ): Boolean {
+            val container: RecentsViewContainer =
+                RecentsViewContainer.containerFromContext(taskContainer.taskView.context)
+            val taskMenuView =
+                container.layoutInflater.inflate(R.layout.task_menu, container.dragLayer, false)
+                    as TaskMenuView
+            taskMenuView.setOnClosingStartCallback(onClosingStartCallback)
+            return taskMenuView.populateAndShowForTask(taskContainer)
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 0e5382a..27db6d6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -199,7 +199,7 @@
          */
         get() = (getNonGridTrans(nonGridTranslationX) + getGridTrans(this.gridTranslationX))
 
-    protected val persistentTranslationY: Float
+    val persistentTranslationY: Float
         /**
          * Returns addition of translationY that is persistent (e.g. fullscreen and grid), and does
          * not change according to a temporary state (e.g. task offset).
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 8c6555e..78ad04b 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -70,6 +70,7 @@
     public static final int WORKSPACE_PAGE_INDICATOR = 1 << 5;
     public static final int SPLIT_PLACHOLDER_VIEW = 1 << 6;
     public static final int FLOATING_SEARCH_BAR = 1 << 7;
+    public static final int ADD_DESK_BUTTON = 1 << 8;
 
     // Flag indicating workspace has multiple pages visible.
     public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0);