Merge "Extend USB compliance warning API" into main
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 87324db..58ee2b2 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -19,6 +19,7 @@
 import static android.os.Trace.TRACE_TAG_APP;
 import static android.provider.DeviceConfig.NAMESPACE_LATENCY_TRACKER;
 
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_BACK_SYSTEM_ANIMATION;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL_UNLOCKED;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_EXPAND_PANEL;
@@ -228,6 +229,11 @@
      */
     public static final int ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME = 24;
 
+    /**
+     * Time it takes to start back preview surface animation after a back gesture starts.
+     */
+    public static final int ACTION_BACK_SYSTEM_ANIMATION = 25;
+
     private static final int[] ACTIONS_ALL = {
         ACTION_EXPAND_PANEL,
         ACTION_TOGGLE_RECENTS,
@@ -254,6 +260,7 @@
         ACTION_SMARTSPACE_DOORBELL,
         ACTION_NOTIFICATION_BIG_PICTURE_LOADED,
         ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME,
+        ACTION_BACK_SYSTEM_ANIMATION,
     };
 
     /** @hide */
@@ -283,6 +290,7 @@
         ACTION_SMARTSPACE_DOORBELL,
         ACTION_NOTIFICATION_BIG_PICTURE_LOADED,
         ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME,
+        ACTION_BACK_SYSTEM_ANIMATION,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Action {
@@ -315,6 +323,7 @@
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_SMARTSPACE_DOORBELL,
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATION_BIG_PICTURE_LOADED,
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_BACK_SYSTEM_ANIMATION,
     };
 
     private final Object mLock = new Object();
@@ -503,6 +512,8 @@
                 return "ACTION_NOTIFICATION_BIG_PICTURE_LOADED";
             case UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME:
                 return "ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME";
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_BACK_SYSTEM_ANIMATION:
+                return "ACTION_BACK_SYSTEM_ANIMATION";
             default:
                 throw new IllegalArgumentException("Invalid action");
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 03c546d..5843635 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -61,6 +61,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.LatencyTracker;
 import com.android.internal.view.AppearanceRegion;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
@@ -99,6 +100,7 @@
      * Max duration to wait for an animation to finish before triggering the real back.
      */
     private static final long MAX_ANIMATION_DURATION = 2000;
+    private final LatencyTracker mLatencyTracker;
 
     /** True when a back gesture is ongoing */
     private boolean mBackGestureStarted = false;
@@ -167,6 +169,7 @@
 
     private final BackAnimationBackground mAnimationBackground;
     private StatusBarCustomizer mCustomizer;
+    private boolean mTrackingLatency;
 
     public BackAnimationController(
             @NonNull ShellInit shellInit,
@@ -213,6 +216,7 @@
                 .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
                 .build();
         mShellBackAnimationRegistry = shellBackAnimationRegistry;
+        mLatencyTracker = LatencyTracker.getInstance(mContext);
     }
 
     private void onInit() {
@@ -438,6 +442,7 @@
 
     private void startBackNavigation(@NonNull TouchTracker touchTracker) {
         try {
+            startLatencyTracking();
             mBackNavigationInfo = mActivityTaskManager.startBackNavigation(
                     mNavigationObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null);
             onBackNavigationInfoReceived(mBackNavigationInfo, touchTracker);
@@ -452,6 +457,7 @@
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Received backNavigationInfo:%s", backNavigationInfo);
         if (backNavigationInfo == null) {
             ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Received BackNavigationInfo is null.");
+            cancelLatencyTracking();
             return;
         }
         final int backType = backNavigationInfo.getType();
@@ -462,6 +468,8 @@
             }
         } else {
             mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
+            // App is handling back animation. Cancel system animation latency tracking.
+            cancelLatencyTracking();
             dispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null));
         }
     }
@@ -808,12 +816,36 @@
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
         mActiveCallback = null;
         mShellBackAnimationRegistry.resetDefaultCrossActivity();
+        cancelLatencyTracking();
         if (mBackNavigationInfo != null) {
             mBackNavigationInfo.onBackNavigationFinished(triggerBack);
             mBackNavigationInfo = null;
         }
     }
 
+    private void startLatencyTracking() {
+        if (mTrackingLatency) {
+            cancelLatencyTracking();
+        }
+        mLatencyTracker.onActionStart(LatencyTracker.ACTION_BACK_SYSTEM_ANIMATION);
+        mTrackingLatency = true;
+    }
+
+    private void cancelLatencyTracking() {
+        if (!mTrackingLatency) {
+            return;
+        }
+        mLatencyTracker.onActionCancel(LatencyTracker.ACTION_BACK_SYSTEM_ANIMATION);
+        mTrackingLatency = false;
+    }
+
+    private void endLatencyTracking() {
+        if (!mTrackingLatency) {
+            return;
+        }
+        mLatencyTracker.onActionEnd(LatencyTracker.ACTION_BACK_SYSTEM_ANIMATION);
+        mTrackingLatency = false;
+    }
 
     private void createAdapter() {
         IBackAnimationRunner runner =
@@ -826,6 +858,7 @@
                             IBackAnimationFinishedCallback finishedCallback) {
                         mShellExecutor.execute(
                                 () -> {
+                                    endLatencyTracking();
                                     if (mBackNavigationInfo == null) {
                                         ProtoLog.e(WM_SHELL_BACK_PREVIEW,
                                                 "Lack of navigation info to start animation.");
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 1941d66..652a2ed 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
@@ -70,8 +70,8 @@
     private int mMarginMenuStart;
     private int mMenuHeight;
     private int mMenuWidth;
-
     private final int mCaptionHeight;
+    private HandleMenuAnimator mHandleMenuAnimator;
 
 
     HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY,
@@ -111,20 +111,19 @@
         mHandleMenuWindow = mParentDecor.addWindow(
                 R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
                 t, ssg, x, y, mMenuWidth, mMenuHeight);
+        final View handleMenuView = mHandleMenuWindow.mWindowViewHost.getView();
+        mHandleMenuAnimator = new HandleMenuAnimator(handleMenuView, mMenuWidth, mCaptionHeight);
     }
 
     /**
      * 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();
+            mHandleMenuAnimator.animateCaptionHandleExpandToOpen();
         } else {
-            handleMenuAnimator.animateOpen();
+            mHandleMenuAnimator.animateOpen();
         }
     }
 
@@ -328,8 +327,16 @@
     }
 
     void close() {
-        mHandleMenuWindow.releaseView();
-        mHandleMenuWindow = null;
+        final Runnable after = () -> {
+            mHandleMenuWindow.releaseView();
+            mHandleMenuWindow = null;
+        };
+        if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+            mHandleMenuAnimator.animateCollapseIntoHandleClose(after);
+        } else {
+            mHandleMenuAnimator.animateClose(after);
+        }
     }
 
     static final class Builder {
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
index 531de1f..8c5d4a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
@@ -26,6 +26,7 @@
 import android.view.View.TRANSLATION_Y
 import android.view.View.TRANSLATION_Z
 import android.view.ViewGroup
+import androidx.core.animation.doOnEnd
 import androidx.core.view.children
 import com.android.wm.shell.R
 import com.android.wm.shell.animation.Interpolators
@@ -37,27 +38,36 @@
     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
+        // Open animation constants
+        private const val MENU_Y_TRANSLATION_OPEN_DURATION: Long = 150
+        private const val HEADER_NONFREEFORM_SCALE_OPEN_DURATION: Long = 150
+        private const val HEADER_FREEFORM_SCALE_OPEN_DURATION: Long = 217
+        private const val HEADER_ELEVATION_OPEN_DURATION: Long = 83
+        private const val HEADER_CONTENT_ALPHA_OPEN_DURATION: Long = 100
+        private const val BODY_SCALE_OPEN_DURATION: Long = 180
+        private const val BODY_ALPHA_OPEN_DURATION: Long = 150
+        private const val BODY_ELEVATION_OPEN_DURATION: Long = 83
+        private const val BODY_CONTENT_ALPHA_OPEN_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 ELEVATION_OPEN_DELAY: Long = 33
+        private const val HEADER_CONTENT_ALPHA_OPEN_DELAY: Long = 67
+        private const val BODY_SCALE_OPEN_DELAY: Long = 50
+        private const val BODY_ALPHA_OPEN_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
+
+        // Close animation constants
+        private const val HEADER_CLOSE_DELAY: Long = 20
+        private const val HEADER_CLOSE_DURATION: Long = 50
+        private const val HEADER_CONTENT_OPACITY_CLOSE_DELAY: Long = 25
+        private const val HEADER_CONTENT_OPACITY_CLOSE_DURATION: Long = 25
+        private const val BODY_CLOSE_DURATION: Long = 50
     }
 
     private val animators: MutableList<Animator> = mutableListOf()
+    private var runningAnimation: AnimatorSet? = null
 
     private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill)
     private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill)
@@ -67,9 +77,9 @@
     fun animateOpen() {
         prepareMenuForAnimation()
         appInfoPillExpand()
-        animateAppInfoPill()
-        animateWindowingPill()
-        animateMoreActionsPill()
+        animateAppInfoPillOpen()
+        animateWindowingPillOpen()
+        animateMoreActionsPillOpen()
         runAnimations()
     }
 
@@ -81,13 +91,44 @@
     fun animateCaptionHandleExpandToOpen() {
         prepareMenuForAnimation()
         captionHandleExpandIntoAppInfoPill()
-        animateAppInfoPill()
-        animateWindowingPill()
-        animateMoreActionsPill()
+        animateAppInfoPillOpen()
+        animateWindowingPillOpen()
+        animateMoreActionsPillOpen()
         runAnimations()
     }
 
     /**
+     * Animates the closing of the handle menu. The windowing and more actions pill vanish. Then,
+     * the app info pill will collapse into the shape of the caption handle in full screen and split
+     * screen.
+     *
+     * @param after runs after the animation finishes.
+     */
+    fun animateCollapseIntoHandleClose(after: Runnable) {
+        appInfoCollapseToHandle()
+        animateAppInfoPillFadeOut()
+        windowingPillClose()
+        moreActionsPillClose()
+        runAnimations(after)
+    }
+
+    /**
+     * Animates the closing of the handle menu. The windowing and more actions pill vanish. Then,
+     * the app info pill will collapse into the shape of the caption handle in full screen and split
+     * screen.
+     *
+     * @param after runs after animation finishes.
+     *
+     */
+    fun animateClose(after: Runnable) {
+        appInfoPillCollapse()
+        animateAppInfoPillFadeOut()
+        windowingPillClose()
+        moreActionsPillClose()
+        runAnimations(after)
+    }
+
+    /**
      * Prepares the handle menu for animation. Presets the opacity of necessary menu components.
      * Presets pivots of handle menu and body pills for scaling animation.
      */
@@ -108,20 +149,20 @@
         moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat()
     }
 
-    private fun animateAppInfoPill() {
+    private fun animateAppInfoPillOpen() {
         // Header Elevation Animation
         animators +=
             ObjectAnimator.ofFloat(appInfoPill, TRANSLATION_Z, 1f).apply {
-                startDelay = ELEVATION_DELAY
-                duration = HEADER_ELEVATION_DURATION
+                startDelay = ELEVATION_OPEN_DELAY
+                duration = HEADER_ELEVATION_OPEN_DURATION
             }
 
         // Content Opacity Animation
         appInfoPill.children.forEach {
             animators +=
                 ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
-                    startDelay = HEADER_CONTENT_ALPHA_DELAY
-                    duration = HEADER_CONTENT_ALPHA_DURATION
+                    startDelay = HEADER_CONTENT_ALPHA_OPEN_DELAY
+                    duration = HEADER_CONTENT_ALPHA_OPEN_DURATION
                 }
         }
     }
@@ -130,17 +171,17 @@
         // Header scaling animation
         animators +=
             ObjectAnimator.ofFloat(appInfoPill, SCALE_X, NONFREEFORM_HEADER_INITIAL_SCALE_X, 1f)
-                .apply { duration = HEADER_NONFREEFORM_SCALE_DURATION }
+                .apply { duration = HEADER_NONFREEFORM_SCALE_OPEN_DURATION }
 
         animators +=
             ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, NONFREEFORM_HEADER_INITIAL_SCALE_Y, 1f)
-                .apply { duration = HEADER_NONFREEFORM_SCALE_DURATION }
+                .apply { duration = HEADER_NONFREEFORM_SCALE_OPEN_DURATION }
 
         // Downward y-translation animation
         val yStart: Float = -captionHeight / 2
         animators +=
             ObjectAnimator.ofFloat(handleMenu, TRANSLATION_Y, yStart, 0f).apply {
-                duration = MENU_Y_TRANSLATION_DURATION
+                duration = MENU_Y_TRANSLATION_OPEN_DURATION
             }
     }
 
@@ -148,98 +189,217 @@
         // Header scaling animation
         animators +=
             ObjectAnimator.ofFloat(appInfoPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
-                duration = HEADER_FREEFORM_SCALE_DURATION
+                duration = HEADER_FREEFORM_SCALE_OPEN_DURATION
             }
 
         animators +=
             ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
-                duration = HEADER_FREEFORM_SCALE_DURATION
+                duration = HEADER_FREEFORM_SCALE_OPEN_DURATION
             }
     }
 
-    private fun animateWindowingPill() {
+    private fun animateWindowingPillOpen() {
         // Windowing X & Y Scaling Animation
         animators +=
             ObjectAnimator.ofFloat(windowingPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
-                startDelay = BODY_SCALE_DELAY
-                duration = BODY_SCALE_DURATION
+                startDelay = BODY_SCALE_OPEN_DELAY
+                duration = BODY_SCALE_OPEN_DURATION
             }
 
         animators +=
             ObjectAnimator.ofFloat(windowingPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
-                startDelay = BODY_SCALE_DELAY
-                duration = BODY_SCALE_DURATION
+                startDelay = BODY_SCALE_OPEN_DELAY
+                duration = BODY_SCALE_OPEN_DURATION
             }
 
         // Windowing Opacity Animation
         animators +=
             ObjectAnimator.ofFloat(windowingPill, ALPHA, 1f).apply {
-                startDelay = BODY_ALPHA_DELAY
-                duration = BODY_ALPHA_DURATION
+                startDelay = BODY_ALPHA_OPEN_DELAY
+                duration = BODY_ALPHA_OPEN_DURATION
             }
 
         // Windowing Elevation Animation
         animators +=
             ObjectAnimator.ofFloat(windowingPill, TRANSLATION_Z, 1f).apply {
-                startDelay = ELEVATION_DELAY
-                duration = BODY_ELEVATION_DURATION
+                startDelay = ELEVATION_OPEN_DELAY
+                duration = BODY_ELEVATION_OPEN_DURATION
             }
 
         // Windowing Content Opacity Animation
         windowingPill.children.forEach {
             animators +=
                 ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
-                    startDelay = BODY_ALPHA_DELAY
-                    duration = BODY_CONTENT_ALPHA_DURATION
+                    startDelay = BODY_ALPHA_OPEN_DELAY
+                    duration = BODY_CONTENT_ALPHA_OPEN_DURATION
                     interpolator = Interpolators.FAST_OUT_SLOW_IN
                 }
         }
     }
 
-    private fun animateMoreActionsPill() {
+    private fun animateMoreActionsPillOpen() {
         // 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
+                startDelay = BODY_SCALE_OPEN_DELAY
+                duration = BODY_SCALE_OPEN_DURATION
             }
 
         animators +=
             ObjectAnimator.ofFloat(moreActionsPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
-                startDelay = BODY_SCALE_DELAY
-                duration = BODY_SCALE_DURATION
+                startDelay = BODY_SCALE_OPEN_DELAY
+                duration = BODY_SCALE_OPEN_DURATION
             }
 
         // More Actions Opacity Animation
         animators +=
             ObjectAnimator.ofFloat(moreActionsPill, ALPHA, 1f).apply {
-                startDelay = BODY_ALPHA_DELAY
-                duration = BODY_ALPHA_DURATION
+                startDelay = BODY_ALPHA_OPEN_DELAY
+                duration = BODY_ALPHA_OPEN_DURATION
             }
 
         // More Actions Elevation Animation
         animators +=
             ObjectAnimator.ofFloat(moreActionsPill, TRANSLATION_Z, 1f).apply {
-                startDelay = ELEVATION_DELAY
-                duration = BODY_ELEVATION_DURATION
+                startDelay = ELEVATION_OPEN_DELAY
+                duration = BODY_ELEVATION_OPEN_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
+                    startDelay = BODY_ALPHA_OPEN_DELAY
+                    duration = BODY_CONTENT_ALPHA_OPEN_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()
+    private fun appInfoPillCollapse() {
+        // Header scaling animation
+        animators +=
+            ObjectAnimator.ofFloat(appInfoPill, SCALE_X, 0f).apply {
+                startDelay = HEADER_CLOSE_DELAY
+                duration = HEADER_CLOSE_DURATION
+            }
+
+        animators +=
+            ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, 0f).apply {
+                startDelay = HEADER_CLOSE_DELAY
+                duration = HEADER_CLOSE_DURATION
+            }
+    }
+
+    private fun appInfoCollapseToHandle() {
+        // Header X & Y Scaling Animation
+        animators +=
+            ObjectAnimator.ofFloat(appInfoPill, SCALE_X, NONFREEFORM_HEADER_INITIAL_SCALE_X).apply {
+                startDelay = HEADER_CLOSE_DELAY
+                duration = HEADER_CLOSE_DURATION
+            }
+
+        animators +=
+            ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, NONFREEFORM_HEADER_INITIAL_SCALE_Y).apply {
+                startDelay = HEADER_CLOSE_DELAY
+                duration = HEADER_CLOSE_DURATION
+            }
+        // Upward y-translation animation
+        val yStart: Float = -captionHeight / 2
+        animators +=
+            ObjectAnimator.ofFloat(appInfoPill, TRANSLATION_Y, yStart).apply {
+                startDelay = HEADER_CLOSE_DELAY
+                duration = HEADER_CLOSE_DURATION
+            }
+    }
+
+    private fun animateAppInfoPillFadeOut() {
+        // Header Content Opacity Animation
+        appInfoPill.children.forEach {
+            animators +=
+                ObjectAnimator.ofFloat(it, ALPHA, 0f).apply {
+                    startDelay = HEADER_CONTENT_OPACITY_CLOSE_DELAY
+                    duration = HEADER_CONTENT_OPACITY_CLOSE_DURATION
+                }
+        }
+    }
+
+    private fun windowingPillClose() {
+        // Windowing X & Y Scaling Animation
+        animators +=
+            ObjectAnimator.ofFloat(windowingPill, SCALE_X, HALF_INITIAL_SCALE).apply {
+                duration = BODY_CLOSE_DURATION
+            }
+
+        animators +=
+            ObjectAnimator.ofFloat(windowingPill, SCALE_Y, HALF_INITIAL_SCALE).apply {
+                duration = BODY_CLOSE_DURATION
+            }
+
+        // windowing Animation
+        animators +=
+            ObjectAnimator.ofFloat(windowingPill, ALPHA, 0f).apply {
+                duration = BODY_CLOSE_DURATION
+            }
+
+        animators +=
+            ObjectAnimator.ofFloat(windowingPill, ALPHA, 0f).apply {
+                duration = BODY_CLOSE_DURATION
+            }
+    }
+
+    private fun moreActionsPillClose() {
+        // More Actions X & Y Scaling Animation
+        animators +=
+            ObjectAnimator.ofFloat(moreActionsPill, SCALE_X, HALF_INITIAL_SCALE).apply {
+                duration = BODY_CLOSE_DURATION
+            }
+
+        animators +=
+            ObjectAnimator.ofFloat(moreActionsPill, SCALE_Y, HALF_INITIAL_SCALE).apply {
+                duration = BODY_CLOSE_DURATION
+            }
+
+        // More Actions Opacity Animation
+        animators +=
+            ObjectAnimator.ofFloat(moreActionsPill, ALPHA, 0f).apply {
+                duration = BODY_CLOSE_DURATION
+            }
+
+        animators +=
+            ObjectAnimator.ofFloat(moreActionsPill, ALPHA, 0f).apply {
+                duration = BODY_CLOSE_DURATION
+            }
+
+        // upward more actions pill y-translation animation
+        val yStart: Float = -captionHeight / 2
+        animators +=
+            ObjectAnimator.ofFloat(moreActionsPill, TRANSLATION_Y, yStart).apply {
+                duration = BODY_CLOSE_DURATION
+            }
+    }
+
+    /**
+     * Runs the list of hide animators concurrently.
+     *
+     * @param after runs after animation finishes.
+     */
+    private fun runAnimations(after: Runnable? = null) {
+        runningAnimation?.apply {
+            // Remove all listeners, so that after runnable isn't triggered upon cancel.
+            removeAllListeners()
+            // If an animation runs while running animation is triggered, gracefully cancel.
+            cancel()
+        }
+
+        runningAnimation = AnimatorSet().apply {
+            playTogether(animators)
+            animators.clear()
+            doOnEnd {
+                after?.run()
+                runningAnimation = null
+            }
+            start()
+        }
     }
 }
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestSceneScope.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestSceneScope.kt
new file mode 100644
index 0000000..de46f72
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestSceneScope.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+
+/** `SceneScope` for tests, which allows a single scene to be drawn in a [SceneTransitionLayout]. */
+@Composable
+fun TestSceneScope(
+    modifier: Modifier = Modifier,
+    content: @Composable SceneScope.() -> Unit,
+) {
+    val currentScene = remember { SceneKey("current") }
+    SceneTransitionLayout(
+        currentScene,
+        onChangeScene = { /* do nothing */},
+        transitions = remember { transitions {} },
+        modifier,
+    ) {
+        scene(currentScene, content = content)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/IRadiiAnimationListener.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/IRadiiAnimationListener.java
index 72935f7..d92b506 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/IRadiiAnimationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/IRadiiAnimationListener.java
@@ -18,4 +18,8 @@
 
 interface IRadiiAnimationListener {
     void onRadiiAnimationUpdate(float[] radii);
+
+    void onRadiiAnimationStart();
+
+    void onRadiiAnimationStop();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index 761551c..34d7cec 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -94,7 +94,19 @@
         mFadeOutAnimator.setDuration(FADE_OUT_DURATION_MS);
         mFadeOutAnimator.addUpdateListener(
                 (animation) -> menuView.setAlpha((float) animation.getAnimatedValue()));
-        mRadiiAnimator = new RadiiAnimator(mMenuViewAppearance.getMenuRadii(), mMenuView::setRadii);
+        mRadiiAnimator = new RadiiAnimator(mMenuViewAppearance.getMenuRadii(),
+                new IRadiiAnimationListener() {
+                    @Override
+                    public void onRadiiAnimationUpdate(float[] radii) {
+                        mMenuView.setRadii(radii);
+                    }
+
+                    @Override
+                    public void onRadiiAnimationStart() {}
+
+                    @Override
+                    public void onRadiiAnimationStop() {}
+                });
     }
 
     void moveToPosition(PointF position) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/RadiiAnimator.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/RadiiAnimator.java
index acad36e..4aa0d89 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/RadiiAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/RadiiAnimator.java
@@ -55,15 +55,19 @@
             @Override
             public void onAnimationStart(@NonNull Animator animation) {
                 animationListener.onRadiiAnimationUpdate(evaluate(/* t = */ 0.0f));
+                animationListener.onRadiiAnimationStart();
             }
 
             @Override
-            public void onAnimationEnd(@NonNull Animator animation) {}
+            public void onAnimationEnd(@NonNull Animator animation) {
+                animationListener.onRadiiAnimationStop();
+            }
 
             @Override
             public void onAnimationCancel(@NonNull Animator animation) {
                 animationListener.onRadiiAnimationUpdate(
                         evaluate(mAnimationDriver.getAnimatedFraction()));
+                animationListener.onRadiiAnimationStop();
             }
 
             @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/RadiiAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/RadiiAnimatorTest.java
index e3a2c59..d77a80a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/RadiiAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/RadiiAnimatorTest.java
@@ -31,42 +31,60 @@
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /** Tests for {@link RadiiAnimator}. */
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class RadiiAnimatorTest extends SysuiTestCase {
     float[] mResultRadii = new float[RadiiAnimator.RADII_COUNT];
+    final AtomicBoolean mAnimationStarted = new AtomicBoolean(false);
+    final AtomicBoolean mAnimationStopped = new AtomicBoolean(false);
+    final IRadiiAnimationListener mRadiiAnimationListener = new IRadiiAnimationListener() {
+        @Override
+        public void onRadiiAnimationUpdate(float[] radii) {
+            mResultRadii = radii;
+        }
+
+        @Override
+        public void onRadiiAnimationStart() {
+            mAnimationStarted.set(true);
+        }
+
+        @Override
+        public void onRadiiAnimationStop() {
+            mAnimationStopped.set(true);
+        }
+    };
 
     @Test
     public void constructor() {
         final float[] radii = generateRadii(0.0f);
-        final RadiiAnimator radiiAnimator = new RadiiAnimator(radii, newRadii -> {});
-
+        final RadiiAnimator radiiAnimator = new RadiiAnimator(radii, mRadiiAnimationListener);
         assertThat(radiiAnimator.evaluate(0.0f)).isEqualTo(radii);
     }
 
     @Test
-    public void skip_updates_to_end() {
+    public void skipAnimation_updatesToEnd() {
         final float[] startRadii = generateRadii(0.0f);
         final float[] endRadii = generateRadii(1.0f);
 
         final RadiiAnimator radiiAnimator = setupAnimator(startRadii);
 
+        mAnimationStarted.set(false);
+        mAnimationStopped.set(false);
         new Handler(Looper.getMainLooper()).post(() -> radiiAnimator.startAnimation(endRadii));
-        TestUtils.waitForCondition(radiiAnimator::isStarted, "Animation did not start.");
+        TestUtils.waitForCondition(mAnimationStarted::get, "Animation did not start.");
         TestUtils.waitForCondition(() -> Arrays.equals(radiiAnimator.evaluate(0.0f), startRadii)
-                                && Arrays.equals(radiiAnimator.evaluate(1.0f), endRadii),
+                        && Arrays.equals(radiiAnimator.evaluate(1.0f), endRadii),
                 "Animator did not initialize to start and end values");
-
         new Handler(Looper.getMainLooper()).post(radiiAnimator::skipAnimationToEnd);
-        TestUtils.waitForCondition(
-                () -> !radiiAnimator.isStarted(), "Animation did not end.");
+        TestUtils.waitForCondition(mAnimationStopped::get, "Animation did not stop.");
         assertThat(mResultRadii).usingTolerance(0.001).containsExactly(endRadii);
     }
 
     @Test
-    public void animation_can_repeat() {
+    public void finishedAnimation_canRepeat() {
         final float[] startRadii = generateRadii(0.0f);
         final float[] midRadii = generateRadii(1.0f);
         final float[] endRadii = generateRadii(2.0f);
@@ -88,15 +106,15 @@
 
     private RadiiAnimator setupAnimator(float[] startRadii) {
         mResultRadii = new float[RadiiAnimator.RADII_COUNT];
-        return new RadiiAnimator(startRadii,
-                newRadii -> mResultRadii = newRadii);
+        return new RadiiAnimator(startRadii, mRadiiAnimationListener);
     }
 
     private void playAndSkipAnimation(RadiiAnimator animator, float[] endRadii) {
+        mAnimationStarted.set(false);
+        mAnimationStopped.set(false);
         new Handler(Looper.getMainLooper()).post(() -> animator.startAnimation(endRadii));
-        TestUtils.waitForCondition(animator::isStarted, "Animation did not start.");
+        TestUtils.waitForCondition(mAnimationStarted::get, "Animation did not start.");
         new Handler(Looper.getMainLooper()).post(animator::skipAnimationToEnd);
-        TestUtils.waitForCondition(
-                () -> !animator.isStarted(), "Animation did not end.");
+        TestUtils.waitForCondition(mAnimationStopped::get, "Animation did not stop.");
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 10110e2..f97d6b3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.scene
 
+import android.content.Context
 import android.content.pm.UserInfo
 import android.graphics.Bitmap
 import android.graphics.drawable.BitmapDrawable
@@ -81,8 +82,10 @@
  */
 @OptIn(ExperimentalCoroutinesApi::class)
 class SceneTestUtils(
-    test: SysuiTestCase,
+    private val context: Context,
 ) {
+    constructor(test: SysuiTestCase) : this(context = test.context)
+
     val kosmos = Kosmos()
     val testDispatcher = kosmos.testDispatcher
     val testScope = kosmos.testScope
@@ -115,8 +118,6 @@
         }
     }
 
-    private val context = test.context
-
     private val falsingCollectorFake: FalsingCollector by lazy { FalsingCollectorFake() }
     private var falsingInteractor: FalsingInteractor? = null
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 216f45a..d722f2f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -279,6 +279,7 @@
                 mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
                         Context.BIND_AUTO_CREATE | Context.BIND_TREAT_LIKE_ACTIVITY
                                 | Context.BIND_SCHEDULE_LIKE_TOP_APP
+                                | Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
                                 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
                         new UserHandle(mUser));
             }
diff --git a/tools/fonts/update_font_metadata.py b/tools/fonts/update_font_metadata.py
index c07a98a..04a5528 100755
--- a/tools/fonts/update_font_metadata.py
+++ b/tools/fonts/update_font_metadata.py
@@ -19,7 +19,7 @@
     args_parser.add_argument('--revision', help='Updated font revision. Use + to update revision based on the current revision')
     args = args_parser.parse_args()
 
-    font = ttLib.TTFont(args.input)
+    font = ttLib.TTFont(args.input, recalcTimestamp=False)
     update_font_revision(font, args.revision)
     font.save(args.output)