diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index ee9f070f67653e4d79d70fb00a22715b18f99efb..87e0b2867090c13d1b3fb11f9a723442aeb15191 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -22,6 +22,7 @@
     android:orientation="vertical">
 
     <LinearLayout
+        android:id="@+id/app_info_pill"
         android:layout_width="match_parent"
         android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height"
         android:layout_marginTop="@dimen/desktop_mode_handle_menu_margin_top"
@@ -66,6 +67,7 @@
     </LinearLayout>
 
     <LinearLayout
+        android:id="@+id/windowing_pill"
         android:layout_width="match_parent"
         android:layout_height="@dimen/desktop_mode_handle_menu_windowing_pill_height"
         android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
@@ -116,6 +118,7 @@
     </LinearLayout>
 
     <LinearLayout
+        android:id="@+id/more_actions_pill"
         android:layout_width="match_parent"
         android:layout_height="@dimen/desktop_mode_handle_menu_more_actions_pill_height"
         android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index bb262d3df07fc92dd835cf8791566cbc7b8f9ef0..3aed9ebc6c5eac6005e6be817ea5b8e3da820027 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -455,7 +455,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
     }
 
     /**
-     * Create and display handle menu window
+     * Create and display handle menu window.
      */
     void createHandleMenu() {
         mHandleMenu = new HandleMenu.Builder(this)
@@ -466,15 +466,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
                 .setLayoutId(mRelayoutParams.mLayoutResId)
                 .setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY)
                 .setWindowingButtonsVisible(DesktopModeStatus.isEnabled())
+                .setCaptionHeight(mResult.mCaptionHeight)
                 .build();
+        mWindowDecorViewHolder.onHandleMenuOpened();
         mHandleMenu.show();
     }
 
     /**
-     * Close the handle menu window
+     * Close the handle menu window.
      */
     void closeHandleMenu() {
         if (!isHandleMenuActive()) return;
+        mWindowDecorViewHolder.onHandleMenuClosed();
         mHandleMenu.close();
         mHandleMenu = null;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index 15f8f1cfadf2d38b31533ff310d4a68a93ed04e8..6391518b5911745ec1ce46e8830e070ecbba0993 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -71,10 +71,13 @@ class HandleMenu {
     private int mMenuHeight;
     private int mMenuWidth;
 
+    private final int mCaptionHeight;
+
 
     HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY,
             View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
-            Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill) {
+            Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill,
+            int captionHeight) {
         mParentDecor = parentDecor;
         mContext = mParentDecor.mDecorWindowContext;
         mTaskInfo = mParentDecor.mTaskInfo;
@@ -86,6 +89,7 @@ class HandleMenu {
         mAppIcon = appIcon;
         mAppName = appName;
         mShouldShowWindowingPill = shouldShowWindowingPill;
+        mCaptionHeight = captionHeight;
         loadHandleMenuDimensions();
         updateHandleMenuPillPositions();
     }
@@ -98,6 +102,7 @@ class HandleMenu {
         ssg.addTransaction(t);
         ssg.markSyncReady();
         setupHandleMenu();
+        animateHandleMenu();
     }
 
     private void createHandleMenuWindow(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
@@ -108,6 +113,21 @@ class HandleMenu {
                 t, ssg, x, y, mMenuWidth, mMenuHeight);
     }
 
+    /**
+     * Animates the appearance of the handle menu and its three pills.
+     */
+    private void animateHandleMenu() {
+        final View handleMenuView = mHandleMenuWindow.mWindowViewHost.getView();
+        final HandleMenuAnimator handleMenuAnimator = new HandleMenuAnimator(handleMenuView,
+                mMenuWidth, mCaptionHeight);
+        if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+            handleMenuAnimator.animateCaptionHandleExpandToOpen();
+        } else {
+            handleMenuAnimator.animateOpen();
+        }
+    }
+
     /**
      * Set up all three pills of the handle menu: app info pill, windowing pill, & more actions
      * pill.
@@ -322,6 +342,7 @@ class HandleMenu {
         private int mCaptionX;
         private int mCaptionY;
         private boolean mShowWindowingPill;
+        private int mCaptionHeight;
 
 
         Builder(@NonNull WindowDecoration parent) {
@@ -364,9 +385,14 @@ class HandleMenu {
             return this;
         }
 
+        Builder setCaptionHeight(int captionHeight) {
+            mCaptionHeight = captionHeight;
+            return this;
+        }
+
         HandleMenu build() {
             return new HandleMenu(mParent, mLayoutId, mCaptionX, mCaptionY, mOnClickListener,
-                    mOnTouchListener, mAppIcon, mName, mShowWindowingPill);
+                    mOnTouchListener, mAppIcon, mName, mShowWindowingPill, mCaptionHeight);
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
new file mode 100644
index 0000000000000000000000000000000000000000..531de1f79ea82de94f7dbdb6a507e26e08838749
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.windowdecor
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.view.View
+import android.view.View.ALPHA
+import android.view.View.SCALE_X
+import android.view.View.SCALE_Y
+import android.view.View.TRANSLATION_Y
+import android.view.View.TRANSLATION_Z
+import android.view.ViewGroup
+import androidx.core.view.children
+import com.android.wm.shell.R
+import com.android.wm.shell.animation.Interpolators
+
+/** Animates the Handle Menu opening. */
+class HandleMenuAnimator(
+    private val handleMenu: View,
+    private val menuWidth: Int,
+    private val captionHeight: Float
+) {
+    companion object {
+        private const val MENU_Y_TRANSLATION_DURATION: Long = 150
+        private const val HEADER_NONFREEFORM_SCALE_DURATION: Long = 150
+        private const val HEADER_FREEFORM_SCALE_DURATION: Long = 217
+        private const val HEADER_ELEVATION_DURATION: Long = 83
+        private const val HEADER_CONTENT_ALPHA_DURATION: Long = 100
+        private const val BODY_SCALE_DURATION: Long = 180
+        private const val BODY_ALPHA_DURATION: Long = 150
+        private const val BODY_ELEVATION_DURATION: Long = 83
+        private const val BODY_CONTENT_ALPHA_DURATION: Long = 167
+
+        private const val ELEVATION_DELAY: Long = 33
+        private const val HEADER_CONTENT_ALPHA_DELAY: Long = 67
+        private const val BODY_SCALE_DELAY: Long = 50
+        private const val BODY_ALPHA_DELAY: Long = 133
+
+        private const val HALF_INITIAL_SCALE: Float = 0.5f
+        private const val NONFREEFORM_HEADER_INITIAL_SCALE_X: Float = 0.6f
+        private const val NONFREEFORM_HEADER_INITIAL_SCALE_Y: Float = 0.05f
+    }
+
+    private val animators: MutableList<Animator> = mutableListOf()
+
+    private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill)
+    private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill)
+    private val moreActionsPill: ViewGroup = handleMenu.requireViewById(R.id.more_actions_pill)
+
+    /** Animates the opening of the handle menu. */
+    fun animateOpen() {
+        prepareMenuForAnimation()
+        appInfoPillExpand()
+        animateAppInfoPill()
+        animateWindowingPill()
+        animateMoreActionsPill()
+        runAnimations()
+    }
+
+    /**
+     * Animates the opening of the handle menu. The caption handle in full screen and split screen
+     * will expand until it assumes the shape of the app info pill. Then, the other two pills will
+     * appear.
+     */
+    fun animateCaptionHandleExpandToOpen() {
+        prepareMenuForAnimation()
+        captionHandleExpandIntoAppInfoPill()
+        animateAppInfoPill()
+        animateWindowingPill()
+        animateMoreActionsPill()
+        runAnimations()
+    }
+
+    /**
+     * Prepares the handle menu for animation. Presets the opacity of necessary menu components.
+     * Presets pivots of handle menu and body pills for scaling animation.
+     */
+    private fun prepareMenuForAnimation() {
+        // Preset opacity
+        appInfoPill.children.forEach { it.alpha = 0f }
+        windowingPill.alpha = 0f
+        moreActionsPill.alpha = 0f
+
+        // Setup pivots.
+        handleMenu.pivotX = menuWidth / 2f
+        handleMenu.pivotY = 0f
+
+        windowingPill.pivotX = menuWidth / 2f
+        windowingPill.pivotY = appInfoPill.measuredHeight.toFloat()
+
+        moreActionsPill.pivotX = menuWidth / 2f
+        moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat()
+    }
+
+    private fun animateAppInfoPill() {
+        // Header Elevation Animation
+        animators +=
+            ObjectAnimator.ofFloat(appInfoPill, TRANSLATION_Z, 1f).apply {
+                startDelay = ELEVATION_DELAY
+                duration = HEADER_ELEVATION_DURATION
+            }
+
+        // Content Opacity Animation
+        appInfoPill.children.forEach {
+            animators +=
+                ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+                    startDelay = HEADER_CONTENT_ALPHA_DELAY
+                    duration = HEADER_CONTENT_ALPHA_DURATION
+                }
+        }
+    }
+
+    private fun captionHandleExpandIntoAppInfoPill() {
+        // Header scaling animation
+        animators +=
+            ObjectAnimator.ofFloat(appInfoPill, SCALE_X, NONFREEFORM_HEADER_INITIAL_SCALE_X, 1f)
+                .apply { duration = HEADER_NONFREEFORM_SCALE_DURATION }
+
+        animators +=
+            ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, NONFREEFORM_HEADER_INITIAL_SCALE_Y, 1f)
+                .apply { duration = HEADER_NONFREEFORM_SCALE_DURATION }
+
+        // Downward y-translation animation
+        val yStart: Float = -captionHeight / 2
+        animators +=
+            ObjectAnimator.ofFloat(handleMenu, TRANSLATION_Y, yStart, 0f).apply {
+                duration = MENU_Y_TRANSLATION_DURATION
+            }
+    }
+
+    private fun appInfoPillExpand() {
+        // Header scaling animation
+        animators +=
+            ObjectAnimator.ofFloat(appInfoPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+                duration = HEADER_FREEFORM_SCALE_DURATION
+            }
+
+        animators +=
+            ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+                duration = HEADER_FREEFORM_SCALE_DURATION
+            }
+    }
+
+    private fun animateWindowingPill() {
+        // Windowing X & Y Scaling Animation
+        animators +=
+            ObjectAnimator.ofFloat(windowingPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+                startDelay = BODY_SCALE_DELAY
+                duration = BODY_SCALE_DURATION
+            }
+
+        animators +=
+            ObjectAnimator.ofFloat(windowingPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+                startDelay = BODY_SCALE_DELAY
+                duration = BODY_SCALE_DURATION
+            }
+
+        // Windowing Opacity Animation
+        animators +=
+            ObjectAnimator.ofFloat(windowingPill, ALPHA, 1f).apply {
+                startDelay = BODY_ALPHA_DELAY
+                duration = BODY_ALPHA_DURATION
+            }
+
+        // Windowing Elevation Animation
+        animators +=
+            ObjectAnimator.ofFloat(windowingPill, TRANSLATION_Z, 1f).apply {
+                startDelay = ELEVATION_DELAY
+                duration = BODY_ELEVATION_DURATION
+            }
+
+        // Windowing Content Opacity Animation
+        windowingPill.children.forEach {
+            animators +=
+                ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+                    startDelay = BODY_ALPHA_DELAY
+                    duration = BODY_CONTENT_ALPHA_DURATION
+                    interpolator = Interpolators.FAST_OUT_SLOW_IN
+                }
+        }
+    }
+
+    private fun animateMoreActionsPill() {
+        // More Actions X & Y Scaling Animation
+        animators +=
+            ObjectAnimator.ofFloat(moreActionsPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+                startDelay = BODY_SCALE_DELAY
+                duration = BODY_SCALE_DURATION
+            }
+
+        animators +=
+            ObjectAnimator.ofFloat(moreActionsPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+                startDelay = BODY_SCALE_DELAY
+                duration = BODY_SCALE_DURATION
+            }
+
+        // More Actions Opacity Animation
+        animators +=
+            ObjectAnimator.ofFloat(moreActionsPill, ALPHA, 1f).apply {
+                startDelay = BODY_ALPHA_DELAY
+                duration = BODY_ALPHA_DURATION
+            }
+
+        // More Actions Elevation Animation
+        animators +=
+            ObjectAnimator.ofFloat(moreActionsPill, TRANSLATION_Z, 1f).apply {
+                startDelay = ELEVATION_DELAY
+                duration = BODY_ELEVATION_DURATION
+            }
+
+        // More Actions Content Opacity Animation
+        moreActionsPill.children.forEach {
+            animators +=
+                ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+                    startDelay = BODY_ALPHA_DELAY
+                    duration = BODY_CONTENT_ALPHA_DURATION
+                    interpolator = Interpolators.FAST_OUT_SLOW_IN
+                }
+        }
+    }
+
+    /** Runs the list of animators concurrently. */
+    private fun runAnimations() {
+        val animatorSet = AnimatorSet()
+        animatorSet.playTogether(animators)
+        animatorSet.start()
+        animators.clear()
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 044c0331282c93f45e3a6481c322275f4267ede3..634b7558c7d81360998732f6aa4b4be1edc6ca59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -269,10 +269,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
                     .build();
         }
 
-        final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
+        outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
         final int captionWidth = taskBounds.width();
 
-        startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
+        startT.setWindowCrop(mCaptionContainerSurface, captionWidth, outResult.mCaptionHeight)
                 .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
                 .show(mCaptionContainerSurface);
 
@@ -283,7 +283,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
             mCaptionInsetsRect.set(taskBounds);
             if (mIsCaptionVisible) {
                 mCaptionInsetsRect.bottom =
-                        mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
+                        mCaptionInsetsRect.top + outResult.mCaptionHeight + params.mCaptionY;
                 wct.addInsetsSource(mTaskInfo.token,
                         mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
                 wct.addInsetsSource(mTaskInfo.token,
@@ -348,7 +348,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
         // Caption view
         mCaptionWindowManager.setConfiguration(taskConfig);
         final WindowManager.LayoutParams lp =
-                new WindowManager.LayoutParams(captionWidth, captionHeight,
+                new WindowManager.LayoutParams(captionWidth, outResult.mCaptionHeight,
                         WindowManager.LayoutParams.TYPE_APPLICATION,
                         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
         lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
@@ -569,6 +569,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
     }
 
     static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
+        int mCaptionHeight;
         int mWidth;
         int mHeight;
         T mRootView;
@@ -576,6 +577,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
         void reset() {
             mWidth = 0;
             mHeight = 0;
+            mCaptionHeight = 0;
             mRootView = null;
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 6b59ccec5148bf9efcd0c807c98c514bd40e8aab..400dec4df50645fc20e809f430835f5b50d11bfa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -48,7 +48,6 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
     }
 
     override fun bindData(taskInfo: RunningTaskInfo) {
-
         val captionDrawable = captionView.background as GradientDrawable
         taskInfo.taskDescription?.statusBarColor?.let {
             captionDrawable.setColor(it)
@@ -63,6 +62,10 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
         appNameTextView.setTextColor(getCaptionAppNameTextColor(taskInfo))
     }
 
+    override fun onHandleMenuOpened() {}
+
+    override fun onHandleMenuClosed() {}
+
     private fun getCaptionAppNameTextColor(taskInfo: RunningTaskInfo): Int {
         return if (shouldUseLightCaptionColors(taskInfo)) {
             context.getColor(R.color.desktop_mode_caption_app_name_light)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 9374ac95e83dd9e51a723fb51a295acab9d8c615..9dc86db4f59bb2c2aee1222cef8852e9458d83a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -1,11 +1,13 @@
 package com.android.wm.shell.windowdecor.viewholder
 
+import android.animation.ObjectAnimator
 import android.app.ActivityManager.RunningTaskInfo
 import android.content.res.ColorStateList
 import android.graphics.drawable.GradientDrawable
 import android.view.View
 import android.widget.ImageButton
 import com.android.wm.shell.R
+import com.android.wm.shell.animation.Interpolators
 
 /**
  * A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen). It
@@ -17,6 +19,10 @@ internal class DesktopModeFocusedWindowDecorationViewHolder(
         onCaptionButtonClickListener: View.OnClickListener
 ) : DesktopModeWindowDecorationViewHolder(rootView) {
 
+    companion object {
+        private const val CAPTION_HANDLE_ANIMATION_DURATION: Long = 100
+    }
+
     private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
     private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
 
@@ -35,6 +41,14 @@ internal class DesktopModeFocusedWindowDecorationViewHolder(
         captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
     }
 
+    override fun onHandleMenuOpened() {
+        animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
+    }
+
+    override fun onHandleMenuClosed() {
+        animateCaptionHandleAlpha(startValue = 1f, endValue = 0f)
+    }
+
     private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
         return if (shouldUseLightCaptionColors(taskInfo)) {
             context.getColor(R.color.desktop_mode_caption_handle_bar_light)
@@ -42,4 +56,14 @@ internal class DesktopModeFocusedWindowDecorationViewHolder(
             context.getColor(R.color.desktop_mode_caption_handle_bar_dark)
         }
     }
+
+    /** Animate appearance/disappearance of caption handle as the handle menu is animated. */
+    private fun animateCaptionHandleAlpha(startValue: Float, endValue: Float) {
+        val animator =
+            ObjectAnimator.ofFloat(captionHandle, View.ALPHA, startValue, endValue).apply {
+                duration = CAPTION_HANDLE_ANIMATION_DURATION
+                interpolator = Interpolators.FAST_OUT_SLOW_IN
+            }
+        animator.start()
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index 49e8d15dcc02db21fc07192d889263cef8028ee1..8b405f02ef2965a6faf815fbdd565238fd0b2759 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -35,4 +35,10 @@ internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) {
           }
         } ?: false
   }
+
+    /** Callback when the handle menu is opened. */
+    abstract fun onHandleMenuOpened()
+
+    /** Callback when the handle menu is closed. */
+    abstract fun onHandleMenuClosed()
 }