Merge "Add unit test for Keyboards & HomeScaffold"
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index da54da16..58ee59d 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -31,6 +31,10 @@
  *
  * The insets frame will by default as the window frame size. If the providers are set, the
  * calculation result based on the source size will be used as the insets frame.
+ *
+ * The InsetsFrameProvider should be self-contained. Nothing describing the window itself, such as
+ * contentInsets, visibleInsets, etc. won't affect the insets providing to other windows when this
+ * is set.
  * @hide
  */
 public class InsetsFrameProvider implements Parcelable {
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
index 573db0d..384dacf 100644
--- a/core/java/android/window/StartingWindowRemovalInfo.java
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -61,6 +61,12 @@
      */
     public boolean deferRemoveForIme;
 
+    /**
+     * The rounded corner radius
+     * @hide
+     */
+    public float roundedCornerRadius;
+
     public StartingWindowRemovalInfo() {
 
     }
@@ -80,6 +86,7 @@
         mainFrame = source.readTypedObject(Rect.CREATOR);
         playRevealAnimation = source.readBoolean();
         deferRemoveForIme = source.readBoolean();
+        roundedCornerRadius = source.readFloat();
     }
 
     @Override
@@ -89,6 +96,7 @@
         dest.writeTypedObject(mainFrame, flags);
         dest.writeBoolean(playRevealAnimation);
         dest.writeBoolean(deferRemoveForIme);
+        dest.writeFloat(roundedCornerRadius);
     }
 
     @Override
@@ -96,6 +104,7 @@
         return "StartingWindowRemovalInfo{taskId=" + taskId
                 + " frame=" + mainFrame
                 + " playRevealAnimation=" + playRevealAnimation
+                + " roundedCornerRadius=" + roundedCornerRadius
                 + " deferRemoveForIme=" + deferRemoveForIme + "}";
     }
 
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index d1d14f6..44923b6 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -117,7 +117,8 @@
     public void testSendHint() {
         Session s = createSession();
         assumeNotNull(s);
-        s.sendHint(Session.CPU_LOAD_UP);
+        s.sendHint(Session.CPU_LOAD_RESET);
+        // ensure we can also send within the rate limit without exception
         s.sendHint(Session.CPU_LOAD_RESET);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index 8bba4404..20da877 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -50,13 +50,17 @@
     private final float mIconStartAlpha;
     private final float mBrandingStartAlpha;
     private final TransactionPool mTransactionPool;
+    // TODO(b/261167708): Clean enter animation code after moving Letterbox code to Shell
+    private final float mRoundedCornerRadius;
 
     private Runnable mFinishCallback;
 
     SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
-            Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish) {
+            Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish,
+            float roundedCornerRadius) {
         mSplashScreenView = view;
         mFirstWindowSurface = leash;
+        mRoundedCornerRadius = roundedCornerRadius;
         if (frame != null) {
             mFirstWindowFrame.set(frame);
         }
@@ -97,7 +101,7 @@
         SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
                 mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
                 mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
-                mAppRevealDuration, this);
+                mAppRevealDuration, this, mRoundedCornerRadius);
     }
 
     private void reset() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index 3098e55..a7e4385 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -63,6 +63,24 @@
 
     /**
      * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
+     * window with rounded corner radius.
+     */
+    static void startAnimations(ViewGroup splashScreenView,
+            SurfaceControl firstWindowSurface, int mainWindowShiftLength,
+            TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+            int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+            int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
+            float roundedCornerRadius) {
+        ValueAnimator animator =
+                createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+                        transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+                        iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+                        animatorListener, roundedCornerRadius);
+        animator.start();
+    }
+
+    /**
+     * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
      * window.
      * @hide
      */
@@ -71,12 +89,10 @@
             TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
             int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
             int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
-        ValueAnimator animator =
-                createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
-                        transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
-                        iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
-                        animatorListener);
-        animator.start();
+        startAnimations(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+                transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+                iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+                animatorListener, 0f /* roundedCornerRadius */);
     }
 
     /**
@@ -87,7 +103,8 @@
             SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
             TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
             int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
-            int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+            int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
+            float roundedCornerRadius) {
         // reveal app
         final float transparentRatio = 0.8f;
         final int globalHeight = splashScreenView.getHeight();
@@ -124,7 +141,7 @@
 
             shiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView,
                     firstWindowSurface, splashScreenView, transactionPool, firstWindowFrame,
-                    mMainWindowShiftLength);
+                    mMainWindowShiftLength, roundedCornerRadius);
         }
 
         ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
@@ -289,8 +306,8 @@
         public ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView,
                                 SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
                                 TransactionPool transactionPool, Rect firstWindowFrame,
-                                int mainWindowShiftLength) {
-            mFromYDelta = fromYDelta;
+                                int mainWindowShiftLength, float roundedCornerRadius) {
+            mFromYDelta = fromYDelta - roundedCornerRadius;
             mToYDelta = toYDelta;
             mOccludeHoleView = occludeHoleView;
             mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 0a2027f..ebb957b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -1168,10 +1168,11 @@
      * Create and play the default exit animation for splash screen view.
      */
     void applyExitAnimation(SplashScreenView view, SurfaceControl leash,
-            Rect frame, Runnable finishCallback, long createTime) {
+            Rect frame, Runnable finishCallback, long createTime, float roundedCornerRadius) {
         final Runnable playAnimation = () -> {
             final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext,
-                    view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback);
+                    view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback,
+                    roundedCornerRadius);
             animation.startAnimations();
         };
         if (view.getIconView() == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index ae3f8e9..4f07bfe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -546,7 +546,7 @@
                             mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
                                     removalInfo.windowAnimationLeash, removalInfo.mainFrame,
                                     () -> removeWindowInner(record.mDecorView, true),
-                                    record.mCreateTime);
+                                    record.mCreateTime, removalInfo.roundedCornerRadius);
                         } else {
                             // the SplashScreenView has been copied to client, hide the view to skip
                             // default exit animation
diff --git a/native/android/Android.bp b/native/android/Android.bp
index f1b1d79..254eb44 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -95,6 +95,7 @@
         "libpowermanager",
         "android.hardware.configstore@1.0",
         "android.hardware.configstore-utils",
+        "android.hardware.power-V4-ndk",
         "libnativedisplay",
     ],
 
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 40eb507..9e97bd3 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "perf_hint"
 
+#include <aidl/android/hardware/power/SessionHint.h>
 #include <android/os/IHintManager.h>
 #include <android/os/IHintSession.h>
 #include <android/performance_hint.h>
@@ -25,14 +26,21 @@
 #include <performance_hint_private.h>
 #include <utils/SystemClock.h>
 
+#include <chrono>
 #include <utility>
 #include <vector>
 
 using namespace android;
 using namespace android::os;
 
+using namespace std::chrono_literals;
+
+using AidlSessionHint = aidl::android::hardware::power::SessionHint;
+
 struct APerformanceHintSession;
 
+constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count();
+
 struct APerformanceHintManager {
 public:
     static APerformanceHintManager* getInstance();
@@ -75,6 +83,8 @@
     int64_t mFirstTargetMetTimestamp;
     // Last target hit timestamp
     int64_t mLastTargetMetTimestamp;
+    // Last hint reported from sendHint indexed by hint value
+    std::vector<int64_t> mLastHintSentTimestamp;
     // Cached samples
     std::vector<int64_t> mActualDurationsNanos;
     std::vector<int64_t> mTimestampsNanos;
@@ -147,7 +157,12 @@
         mPreferredRateNanos(preferredRateNanos),
         mTargetDurationNanos(targetDurationNanos),
         mFirstTargetMetTimestamp(0),
-        mLastTargetMetTimestamp(0) {}
+        mLastTargetMetTimestamp(0) {
+    const std::vector<AidlSessionHint> sessionHintRange{ndk::enum_range<AidlSessionHint>().begin(),
+                                                        ndk::enum_range<AidlSessionHint>().end()};
+
+    mLastHintSentTimestamp = std::vector<int64_t>(sessionHintRange.size(), 0);
+}
 
 APerformanceHintSession::~APerformanceHintSession() {
     binder::Status ret = mHintSession->close();
@@ -224,10 +239,16 @@
 }
 
 int APerformanceHintSession::sendHint(int32_t hint) {
-    if (hint < 0) {
-        ALOGE("%s: session hint value must be greater than zero", __FUNCTION__);
+    if (hint < 0 || hint >= static_cast<int32_t>(mLastHintSentTimestamp.size())) {
+        ALOGE("%s: invalid session hint %d", __FUNCTION__, hint);
         return EINVAL;
     }
+    int64_t now = elapsedRealtimeNano();
+
+    // Limit sendHint to a pre-detemined rate for safety
+    if (now < (mLastHintSentTimestamp[hint] + SEND_HINT_TIMEOUT)) {
+        return 0;
+    }
 
     binder::Status ret = mHintSession->sendHint(hint);
 
@@ -235,6 +256,7 @@
         ALOGE("%s: HintSession sendHint failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
         return EPIPE;
     }
+    mLastHintSentTimestamp[hint] = now;
     return 0;
 }
 
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 1881e60..0c2d3b6 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -122,9 +122,16 @@
     result = APerformanceHint_reportActualWorkDuration(session, -1L);
     EXPECT_EQ(EINVAL, result);
 
-    // Send both valid and invalid session hints
     int hintId = 2;
-    EXPECT_CALL(*iSession, sendHint(Eq(2))).Times(Exactly(1));
+    EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(1));
+    result = APerformanceHint_sendHint(session, hintId);
+    EXPECT_EQ(0, result);
+    usleep(110000); // Sleep for longer than the update timeout.
+    EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(1));
+    result = APerformanceHint_sendHint(session, hintId);
+    EXPECT_EQ(0, result);
+    // Expect to get rate limited if we try to send faster than the limiter allows
+    EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(0));
     result = APerformanceHint_sendHint(session, hintId);
     EXPECT_EQ(0, result);
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
index ae325f8..e3e1220 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
@@ -20,6 +20,7 @@
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.ColorDrawable
 import android.graphics.drawable.Drawable
+import android.os.Build
 import android.os.Handler
 import android.os.Looper
 import android.view.View
@@ -117,13 +118,17 @@
         return true
     }
 
-    override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean =
-        drawable.setLayoutDirection(
-            when (layoutDirection) {
-                LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
-                LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
-            }
-        )
+    override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
+        if (Build.VERSION.SDK_INT >= 23) {
+            return drawable.setLayoutDirection(
+                when (layoutDirection) {
+                    LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
+                    LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
+                }
+            )
+        }
+        return false
+    }
 
     override val intrinsicSize: Size get() = drawableIntrinsicSize
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt
deleted file mode 100644
index dbf8836..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright 2022 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.settingslib.spa.framework.compose
-
-import android.annotation.SuppressLint
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.produceState
-import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.repeatOnLifecycle
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.withContext
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
-
-/**
- * *************************************************************************************************
- * This file was forked from AndroidX:
- * lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/FlowExt.kt
- * TODO: Replace with AndroidX when it's usable.
- */
-
-/**
- * Collects values from this [StateFlow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * The [StateFlow.value] is used as an initial value. Every time there would be new value posted
- * into the [StateFlow] the returned [State] will be updated causing recomposition of every
- * [State.value] usage whenever the [lifecycleOwner]'s lifecycle is at least [minActiveState].
- *
- * This [StateFlow] is collected every time the [lifecycleOwner]'s lifecycle reaches the
- * [minActiveState] Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle
- * falls below [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this`
- * flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@SuppressLint("StateFlowValueCalledInComposition")
-@Composable
-fun <T> StateFlow<T>.collectAsStateWithLifecycle(
-    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
-    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
-    context: CoroutineContext = EmptyCoroutineContext
-): State<T> = collectAsStateWithLifecycle(
-    initialValue = this.value,
-    lifecycle = lifecycleOwner.lifecycle,
-    minActiveState = minActiveState,
-    context = context
-)
-
-/**
- * Collects values from this [StateFlow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * The [StateFlow.value] is used as an initial value. Every time there would be new value posted
- * into the [StateFlow] the returned [State] will be updated causing recomposition of every
- * [State.value] usage whenever the [lifecycle] is at least [minActiveState].
- *
- * This [StateFlow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle
- * state. The collection stops when [lifecycle] falls below [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param lifecycle [Lifecycle] used to restart collecting `this` flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@SuppressLint("StateFlowValueCalledInComposition")
-@Composable
-fun <T> StateFlow<T>.collectAsStateWithLifecycle(
-    lifecycle: Lifecycle,
-    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
-    context: CoroutineContext = EmptyCoroutineContext
-): State<T> = collectAsStateWithLifecycle(
-    initialValue = this.value,
-    lifecycle = lifecycle,
-    minActiveState = minActiveState,
-    context = context
-)
-
-/**
- * Collects values from this [Flow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * Every time there would be new value posted into the [Flow] the returned [State] will be updated
- * causing recomposition of every [State.value] usage whenever the [lifecycleOwner]'s lifecycle is
- * at least [minActiveState].
- *
- * This [Flow] is collected every time the [lifecycleOwner]'s lifecycle reaches the [minActiveState]
- * Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle falls below
- * [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param initialValue The initial value given to the returned [State.value].
- * @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this`
- * flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@Composable
-fun <T> Flow<T>.collectAsStateWithLifecycle(
-    initialValue: T,
-    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
-    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
-    context: CoroutineContext = EmptyCoroutineContext
-): State<T> = collectAsStateWithLifecycle(
-    initialValue = initialValue,
-    lifecycle = lifecycleOwner.lifecycle,
-    minActiveState = minActiveState,
-    context = context
-)
-
-/**
- * Collects values from this [Flow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * Every time there would be new value posted into the [Flow] the returned [State] will be updated
- * causing recomposition of every [State.value] usage whenever the [lifecycle] is at
- * least [minActiveState].
- *
- * This [Flow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle
- * state. The collection stops when [lifecycle] falls below [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param initialValue The initial value given to the returned [State.value].
- * @param lifecycle [Lifecycle] used to restart collecting `this` flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@Composable
-fun <T> Flow<T>.collectAsStateWithLifecycle(
-    initialValue: T,
-    lifecycle: Lifecycle,
-    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
-    context: CoroutineContext = EmptyCoroutineContext
-): State<T> {
-    return produceState(initialValue, this, lifecycle, minActiveState, context) {
-        lifecycle.repeatOnLifecycle(minActiveState) {
-            if (context == EmptyCoroutineContext) {
-                this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
-            } else withContext(context) {
-                this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
-            }
-        }
-    }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
index 4df7794..392089a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
@@ -19,8 +19,6 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.calculateEndPadding
-import androidx.compose.foundation.layout.calculateStartPadding
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyRow
@@ -36,7 +34,6 @@
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
@@ -123,7 +120,7 @@
     contentPadding: PaddingValues = PaddingValues(0.dp),
     horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
     key: ((page: Int) -> Any)? = null,
-    content: @Composable() (PagerScope.(page: Int) -> Unit),
+    content: @Composable PagerScope.(page: Int) -> Unit,
 ) {
     Pager(
         count = count,
@@ -175,24 +172,8 @@
             .collect { state.updateCurrentPageBasedOnLazyListState() }
     }
     val density = LocalDensity.current
-    val layoutDirection = LocalLayoutDirection.current
-    LaunchedEffect(density, contentPadding, isVertical, layoutDirection, reverseLayout, state) {
-        with(density) {
-            // this should be exposed on LazyListLayoutInfo instead. b/200920410
-            state.afterContentPadding = if (isVertical) {
-                if (!reverseLayout) {
-                    contentPadding.calculateBottomPadding()
-                } else {
-                    contentPadding.calculateTopPadding()
-                }
-            } else {
-                if (!reverseLayout) {
-                    contentPadding.calculateEndPadding(layoutDirection)
-                } else {
-                    contentPadding.calculateStartPadding(layoutDirection)
-                }
-            }.roundToPx()
-        }
+    LaunchedEffect(density, state, itemSpacing) {
+        with(density) { state.itemSpacing = itemSpacing.roundToPx() }
     }
 
     val pagerScope = remember(state) { PagerScopeImpl(state) }
@@ -203,6 +184,7 @@
         ConsumeFlingNestedScrollConnection(
             consumeHorizontal = !isVertical,
             consumeVertical = isVertical,
+            pagerState = state,
         )
     }
 
@@ -268,6 +250,7 @@
 private class ConsumeFlingNestedScrollConnection(
     private val consumeHorizontal: Boolean,
     private val consumeVertical: Boolean,
+    private val pagerState: PagerState,
 ) : NestedScrollConnection {
     override fun onPostScroll(
         consumed: Offset,
@@ -281,9 +264,15 @@
     }
 
     override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
-        // We can consume all post fling velocity on the main-axis
-        // so that it doesn't propagate up to the Pager
-        return available.consume(consumeHorizontal, consumeVertical)
+        return if (pagerState.currentPageOffset != 0f) {
+            // The Pager is already scrolling. This means that a nested scroll child was
+            // scrolled to end, and the Pager can use this fling
+            Velocity.Zero
+        } else {
+            // A nested scroll child is still scrolling. We can consume all post fling
+            // velocity on the main-axis so that it doesn't propagate up to the Pager
+            available.consume(consumeHorizontal, consumeVertical)
+        }
     }
 }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
index 21ba117..480335d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
@@ -85,12 +85,14 @@
             return layoutInfo.visibleItemsInfo.maxByOrNull {
                 val start = maxOf(it.offset, 0)
                 val end = minOf(
-                    it.offset + it.size, layoutInfo.viewportEndOffset - afterContentPadding)
+                    it.offset + it.size,
+                    layoutInfo.viewportEndOffset - layoutInfo.afterContentPadding
+                )
                 end - start
             }
         }
 
-    internal var afterContentPadding = 0
+    internal var itemSpacing by mutableStateOf(0)
 
     private val currentPageLayoutInfo: LazyListItemInfo?
         get() = lazyListState.layoutInfo.visibleItemsInfo.lastOrNull {
@@ -135,9 +137,7 @@
      */
     val currentPageOffset: Float by derivedStateOf {
         currentPageLayoutInfo?.let {
-            // We coerce since itemSpacing can make the offset > 1f.
-            // We don't want to count spacing in the offset so cap it to 1f
-            (-it.offset / it.size.toFloat()).coerceIn(-1f, 1f)
+            (-it.offset / (it.size + itemSpacing).toFloat()).coerceIn(-0.5f, 0.5f)
         } ?: 0f
     }
 
@@ -187,28 +187,26 @@
                     // offset from the size
                     lazyListState.animateScrollToItem(
                         index = page,
-                        scrollOffset = (target.size * pageOffset).roundToInt()
+                        scrollOffset = ((target.size + itemSpacing) * pageOffset).roundToInt()
                     )
                 } else if (layoutInfo.visibleItemsInfo.isNotEmpty()) {
                     // If we don't, we use the current page size as a guide
-                    val currentSize = layoutInfo.visibleItemsInfo.first().size
+                    val currentSize = layoutInfo.visibleItemsInfo.first().size + itemSpacing
                     lazyListState.animateScrollToItem(
                         index = page,
                         scrollOffset = (currentSize * pageOffset).roundToInt()
                     )
 
                     // The target should be visible now
-                    target = lazyListState.layoutInfo.visibleItemsInfo.firstOrNull {
-                        it.index == page
-                    }
+                    target = layoutInfo.visibleItemsInfo.firstOrNull { it.index == page }
 
-                    if (target != null && target.size != currentSize) {
+                    if (target != null && target.size + itemSpacing != currentSize) {
                         // If the size we used for calculating the offset differs from the actual
                         // target page size, we need to scroll again. This doesn't look great,
                         // but there's not much else we can do.
                         lazyListState.animateScrollToItem(
                             index = page,
-                            scrollOffset = (target.size * pageOffset).roundToInt()
+                            scrollOffset = ((target.size + itemSpacing) * pageOffset).roundToInt()
                         )
                     }
                 }
@@ -248,7 +246,7 @@
             if (pageOffset.absoluteValue > 0.0001f) {
                 currentPageLayoutInfo?.let {
                     scroll {
-                        scrollBy(it.size * pageOffset)
+                        scrollBy((it.size + itemSpacing) * pageOffset)
                     }
                 }
             }
@@ -295,7 +293,7 @@
     }
 
     private fun requireCurrentPageOffset(value: Float, name: String) {
-        require(value in -1f..1f) { "$name must be >= 0 and <= 1" }
+        require(value in -1f..1f) { "$name must be >= -1 and <= 1" }
     }
 
     companion object {
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index c4a17d8..45f9b23 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -76,7 +76,13 @@
     sourceDirectories.from = files("../spa/src")
     classDirectories.from = fileTree(
             dir: "../spa/build/tmp/kotlin-classes/debug",
-            excludes: ["com/android/settingslib/spa/debug/**"],
+            excludes: [
+                    "com/android/settingslib/spa/debug/**",
+
+                    // Excludes files forked from Accompanist.
+                    "com/android/settingslib/spa/framework/compose/DrawablePainter*",
+                    "com/android/settingslib/spa/framework/compose/Pager*",
+            ],
     )
     executionData.from = fileTree(dir: "$buildDir/outputs/code_coverage/debugAndroidTest/connected")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 33c00fb..9349966 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -432,6 +432,11 @@
                             AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER |
                             AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0;
             changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
+        } else if (stream == AudioManager.STREAM_VOICE_CALL) {
+            final boolean routedToBluetooth =
+                    (mAudio.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)
+                            & AudioManager.DEVICE_OUT_BLE_HEADSET) != 0;
+            changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
         }
         return changed;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 833a6a4..1bc0d08 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1771,6 +1771,7 @@
         if (ss.level == row.requestedLevel) {
             row.requestedLevel = -1;
         }
+        final boolean isVoiceCallStream = row.stream == AudioManager.STREAM_VOICE_CALL;
         final boolean isA11yStream = row.stream == STREAM_ACCESSIBILITY;
         final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
         final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
@@ -1815,8 +1816,12 @@
         } else if (isRingSilent || zenMuted) {
             iconRes = row.iconMuteRes;
         } else if (ss.routedToBluetooth) {
-            iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
-                                        : R.drawable.ic_volume_media_bt;
+            if (isVoiceCallStream) {
+                iconRes = R.drawable.ic_volume_bt_sco;
+            } else {
+                iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
+                                            : R.drawable.ic_volume_media_bt;
+            }
         } else if (isStreamMuted(ss)) {
             iconRes = ss.muted ? R.drawable.ic_volume_media_off : row.iconMuteRes;
         } else {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 3769f52..915ea1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -170,6 +170,34 @@
     }
 
     @Test
+    public void testVolumeChangeW_deviceOutFromBLEHeadset_doStateChanged() {
+        mVolumeController.setDeviceInteractive(false);
+        when(mWakefullnessLifcycle.getWakefulness()).thenReturn(
+                WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+        when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn(
+                AudioManager.DEVICE_OUT_BLE_HEADSET);
+
+        mVolumeController.onVolumeChangedW(
+                AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI);
+
+        verify(mCallback, times(1)).onStateChanged(any());
+    }
+
+    @Test
+    public void testVolumeChangeW_deviceOutFromA2DP_doStateChanged() {
+        mVolumeController.setDeviceInteractive(false);
+        when(mWakefullnessLifcycle.getWakefulness()).thenReturn(
+                WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+        when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn(
+                AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
+
+        mVolumeController.onVolumeChangedW(
+                AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI);
+
+        verify(mCallback, never()).onStateChanged(any());
+    }
+
+    @Test
     public void testOnRemoteVolumeChanged_newStream_noNullPointer() {
         MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
         mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 3f8333a..a7e95fb 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -80,7 +80,6 @@
 import android.os.IProgressListener;
 import android.os.IRemoteCallback;
 import android.os.IUserManager;
-import android.os.Looper;
 import android.os.Message;
 import android.os.PowerWhitelistManager;
 import android.os.Process;
@@ -437,6 +436,12 @@
     /** @see #getLastUserUnlockingUptime */
     private volatile long mLastUserUnlockingUptime = 0;
 
+    /**
+     * Pending user starts waiting for shutdown step to complete.
+     */
+    @GuardedBy("mLock")
+    private final List<PendingUserStart> mPendingUserStarts = new ArrayList<>();
+
     private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() {
         @Override
         public void onUserCreated(UserInfo user, Object token) {
@@ -1184,9 +1189,13 @@
             } else {
                 stopped = true;
                 // User can no longer run.
+                Slogf.i(TAG, "Removing user state from UserController.mStartedUsers for user #"
+                        + userId + " as a result of user being stopped");
                 mStartedUsers.remove(userId);
+
                 mUserLru.remove(Integer.valueOf(userId));
                 updateStartedUserArrayLU();
+
                 if (allowDelayedLocking && !keyEvictedCallbacks.isEmpty()) {
                     Slogf.wtf(TAG,
                             "Delayed locking enabled while KeyEvictedCallbacks not empty, userId:"
@@ -1200,7 +1209,10 @@
             }
         }
         if (stopped) {
+            Slogf.i(TAG, "Removing user state from UserManager.mUserStates for user #" + userId
+                    + " as a result of user being stopped");
             mInjector.getUserManagerInternal().removeUserState(userId);
+
             mInjector.activityManagerOnUserStopped(userId);
             // Clean up all state and processes associated with the user.
             // Kill all the processes for the user.
@@ -1228,10 +1240,13 @@
                     USER_LIFECYCLE_EVENT_STATE_FINISH);
             clearSessionId(userId);
 
-            if (!lockUser) {
-                return;
+            if (lockUser) {
+                dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
             }
-            dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
+
+            // Resume any existing pending user start,
+            // which was paused while the SHUTDOWN flow of the user was in progress.
+            resumePendingUserStarts(userId);
         } else {
             logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
                     USER_LIFECYCLE_EVENT_STATE_NONE);
@@ -1239,6 +1254,31 @@
         }
     }
 
+    /**
+     * Resume any existing pending user start for the specified userId which was paused
+     * while the shutdown flow of the user was in progress.
+     * Remove all the handled user starts from mPendingUserStarts.
+     * @param userId the id of the user
+     */
+    private void resumePendingUserStarts(@UserIdInt int userId) {
+        synchronized (mLock) {
+            final List<PendingUserStart> handledUserStarts = new ArrayList<>();
+
+            for (PendingUserStart userStart: mPendingUserStarts) {
+                if (userStart.userId == userId) {
+                    Slogf.i(TAG, "resumePendingUserStart for" + userStart);
+                    mHandler.post(() -> startUser(userStart.userId,
+                            userStart.isForeground, userStart.unlockListener));
+
+                    handledUserStarts.add(userStart);
+                }
+            }
+            // remove all the pending user starts which are now handled
+            mPendingUserStarts.removeAll(handledUserStarts);
+        }
+    }
+
+
     private void dispatchUserLocking(@UserIdInt int userId,
             @Nullable List<KeyEvictedCallback> keyEvictedCallbacks) {
         // Evict the user's credential encryption key. Performed on FgThread to make it
@@ -1252,6 +1292,7 @@
                 }
             }
             try {
+                Slogf.i(TAG, "Locking CE storage for user #" + userId);
                 mInjector.getStorageManager().lockUserKey(userId);
             } catch (RemoteException re) {
                 throw re.rethrowAsRuntimeException();
@@ -1657,10 +1698,11 @@
                     updateStartedUserArrayLU();
                     needStart = true;
                     updateUmState = true;
-                } else if (uss.state == UserState.STATE_SHUTDOWN && !isCallingOnHandlerThread()) {
+                } else if (uss.state == UserState.STATE_SHUTDOWN) {
                     Slogf.i(TAG, "User #" + userId
-                            + " is shutting down - will start after full stop");
-                    mHandler.post(() -> startUser(userId, foreground, unlockListener));
+                            + " is shutting down - will start after full shutdown");
+                    mPendingUserStarts.add(new PendingUserStart(userId,
+                            foreground, unlockListener));
                     t.traceEnd(); // updateStartedUserArrayStarting
                     return true;
                 }
@@ -1818,10 +1860,6 @@
         return true;
     }
 
-    private boolean isCallingOnHandlerThread() {
-        return Looper.myLooper() == mHandler.getLooper();
-    }
-
     /**
      * Start user, if its not already running, and bring it to foreground.
      */
@@ -3390,6 +3428,32 @@
         }
     }
 
+    /**
+     * Helper class for keeping track of user starts which are paused while user's
+     * shutdown is taking place.
+     */
+    private static class PendingUserStart {
+        public final @UserIdInt int userId;
+        public final boolean isForeground;
+        public final IProgressListener unlockListener;
+
+        PendingUserStart(int userId, boolean foreground,
+                IProgressListener unlockListener) {
+            this.userId = userId;
+            this.isForeground = foreground;
+            this.unlockListener = unlockListener;
+        }
+
+        @Override
+        public String toString() {
+            return "PendingUserStart{"
+                    + "userId=" + userId
+                    + ", isForeground=" + isForeground
+                    + ", unlockListener=" + unlockListener
+                    + '}';
+        }
+    }
+
     @VisibleForTesting
     static class Injector {
         private final ActivityManagerService mService;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7dd8770..eb04687 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4386,6 +4386,13 @@
                 values.touchscreen,
                 values.uiMode);
 
+        // Note: certain tests currently run as platform_app which is not allowed
+        // to set debug system properties. To ensure that system properties are set
+        // only when allowed, we check the current UID.
+        if (Process.myUid() == Process.SYSTEM_UID) {
+            SystemProperties.set("debug.tracing.mcc", Integer.toString(values.mcc));
+            SystemProperties.set("debug.tracing.mnc", Integer.toString(values.mnc));
+        }
 
         if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
             final LocaleList locales = values.getLocales();
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 1fef3c2..9ac1762 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1202,7 +1202,6 @@
             return null;
         }
         return (displayFrames, windowContainer, inOutFrame) -> {
-            inOutFrame.inset(win.mGivenContentInsets);
             final LayoutParams lp = win.mAttrs.forRotation(displayFrames.mRotation);
             final InsetsFrameProvider ifp = lp.providedInsets[index];
             InsetsFrameProvider.calculateInsetsFrame(displayFrames.mUnrestricted,
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 0a2e877..274d7ff 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -691,6 +691,8 @@
             if (mainWindow == null || mainWindow.mRemoved) {
                 removalInfo.playRevealAnimation = false;
             } else if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
+                removalInfo.roundedCornerRadius =
+                        topActivity.mLetterboxUiController.getRoundedCornersRadius(mainWindow);
                 removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
                 removalInfo.mainFrame = mainWindow.getRelativeFrame();
             }