Merge "Fixes activity not resumed after applying WCT" into main
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 8ecb1fb..2948129 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -19,6 +19,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.service.dreams.Flags.dreamHandlesConfirmKeys;
 import static android.service.dreams.Flags.dreamHandlesBeingObscured;
+import static android.service.dreams.Flags.startAndStopDozingInBackground;
 
 import android.annotation.FlaggedApi;
 import android.annotation.IdRes;
@@ -923,9 +924,16 @@
 
         if (mDozing) {
             try {
-                mDreamManager.startDozing(
+                if (startAndStopDozingInBackground()) {
+                    mDreamManager.startDozingOneway(
                         mDreamToken, mDozeScreenState, mDozeScreenStateReason,
                         mDozeScreenBrightness);
+                } else {
+                    mDreamManager.startDozing(
+                            mDreamToken, mDozeScreenState, mDozeScreenStateReason,
+                            mDozeScreenBrightness);
+                }
+
             } catch (RemoteException ex) {
                 // system server died
             }
@@ -1250,7 +1258,11 @@
         try {
             // finishSelf will unbind the dream controller from the dream service. This will
             // trigger DreamService.this.onDestroy and DreamService.this will die.
-            mDreamManager.finishSelf(mDreamToken, true /*immediate*/);
+            if (startAndStopDozingInBackground()) {
+                mDreamManager.finishSelfOneway(mDreamToken, true /*immediate*/);
+            } else {
+                mDreamManager.finishSelf(mDreamToken, true /*immediate*/);
+            }
         } catch (RemoteException ex) {
             // system server died
         }
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index cf98bfe0..620eef6 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -50,4 +50,6 @@
     void startDreamActivity(in Intent intent);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)")
     oneway void setDreamIsObscured(in boolean isObscured);
+    oneway void startDozingOneway(in IBinder token, int screenState, int reason, int screenBrightness);
+    oneway void finishSelfOneway(in IBinder token, boolean immediate);
 }
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
index 54d950c..83e0adf 100644
--- a/core/java/android/service/dreams/flags.aconfig
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -47,3 +47,13 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+    name: "start_and_stop_dozing_in_background"
+    namespace: "systemui"
+    description: "Move the start-dozing and stop-dozing operation to the background"
+    bug: "330287187"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
index fc1852d..c7e93c1 100644
--- a/core/java/android/view/ImeBackAnimationController.java
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -27,6 +27,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.WindowConfiguration;
 import android.graphics.Insets;
 import android.util.Log;
 import android.view.animation.BackGestureInterpolator;
@@ -137,9 +138,10 @@
     @Override
     public void onBackInvoked() {
         if (!isBackAnimationAllowed() || !mIsPreCommitAnimationInProgress) {
-            // play regular hide animation if back-animation is not allowed or if insets control has
-            // been cancelled by the system (this can happen in split screen for example)
-            mInsetsController.hide(ime());
+            // play regular hide animation if predictive back-animation is not allowed or if insets
+            // control has been cancelled by the system. This can happen in multi-window mode for
+            // example (i.e. split-screen or activity-embedding)
+            notifyHideIme();
             return;
         }
         startPostCommitAnim(/*hideIme*/ true);
@@ -209,6 +211,11 @@
         if (triggerBack) {
             mInsetsController.setPredictiveBackImeHideAnimInProgress(true);
             notifyHideIme();
+            // requesting IME as invisible during post-commit
+            mInsetsController.setRequestedVisibleTypes(0, ime());
+            // Changes the animation state. This also notifies RootView of changed insets, which
+            // causes it to reset its scrollY to 0f (animated) if it was panned
+            mInsetsController.onAnimationStateChanged(ime(), /*running*/ true);
         }
         if (mStartRootScrollY != 0 && !triggerBack) {
             // This causes RootView to update its scroll back to the panned position
@@ -228,12 +235,6 @@
         // the IME away
         mInsetsController.getHost().getInputMethodManager()
                 .notifyImeHidden(mInsetsController.getHost().getWindowToken(), statsToken);
-
-        // requesting IME as invisible during post-commit
-        mInsetsController.setRequestedVisibleTypes(0, ime());
-        // Changes the animation state. This also notifies RootView of changed insets, which causes
-        // it to reset its scrollY to 0f (animated) if it was panned
-        mInsetsController.onAnimationStateChanged(ime(), /*running*/ true);
     }
 
     private void reset() {
@@ -254,8 +255,18 @@
     }
 
     private boolean isBackAnimationAllowed() {
-        // back animation is allowed in all cases except when softInputMode is adjust_resize AND
-        // there is no app-registered WindowInsetsAnimationCallback AND edge-to-edge is not enabled.
+
+        if (mViewRoot.mContext.getResources().getConfiguration().windowConfiguration
+                .getWindowingMode() == WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW) {
+            // TODO(b/346726115) enable predictive back animation in multi-window mode in
+            //  DisplayImeController
+            return false;
+        }
+
+        // otherwise, the predictive back animation is allowed in all cases except when
+        // 1. softInputMode is adjust_resize AND
+        // 2. there is no app-registered WindowInsetsAnimationCallback AND
+        // 3. edge-to-edge is not enabled.
         return (mViewRoot.mWindowAttributes.softInputMode & SOFT_INPUT_MASK_ADJUST)
                 != SOFT_INPUT_ADJUST_RESIZE
                 || (mViewRoot.mView != null && mViewRoot.mView.hasWindowInsetsAnimationCallback())
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c0bd535..cda58e38 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -7451,6 +7451,10 @@
 
         @Override
         protected int onProcess(QueuedInputEvent q) {
+            if (q.forPreImeOnly()) {
+                // this event is intended for the ViewPreImeInputStage only, let's forward
+                return FORWARD;
+            }
             if (q.mEvent instanceof KeyEvent) {
                 final KeyEvent keyEvent = (KeyEvent) q.mEvent;
 
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 4fb6e69..9b87e23 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -489,7 +489,8 @@
                     return;
                 }
                 OnBackAnimationCallback animationCallback = getBackAnimationCallback();
-                if (animationCallback != null) {
+                if (animationCallback != null
+                        && !(callback instanceof ImeBackAnimationController)) {
                     mProgressAnimator.onBackInvoked(callback::onBackInvoked);
                 } else {
                     mProgressAnimator.reset();
diff --git a/core/res/res/anim/overlay_task_fragment_change.xml b/core/res/res/anim/overlay_task_fragment_change.xml
new file mode 100644
index 0000000..eb02ba8
--- /dev/null
+++ b/core/res/res/anim/overlay_task_fragment_change.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+   -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:showBackdrop="false">
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/overlay_task_fragment_close_to_bottom.xml b/core/res/res/anim/overlay_task_fragment_close_to_bottom.xml
new file mode 100644
index 0000000..d9487cb
--- /dev/null
+++ b/core/res/res/anim/overlay_task_fragment_close_to_bottom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+   -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:fromYDelta="0"
+        android:toYDelta="100%"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:duration="517" />
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/overlay_task_fragment_close_to_left.xml b/core/res/res/anim/overlay_task_fragment_close_to_left.xml
new file mode 100644
index 0000000..3cdb77a
--- /dev/null
+++ b/core/res/res/anim/overlay_task_fragment_close_to_left.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+   -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:fromXDelta="0"
+        android:toXDelta="-100%"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:duration="517" />
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/overlay_task_fragment_close_to_right.xml b/core/res/res/anim/overlay_task_fragment_close_to_right.xml
new file mode 100644
index 0000000..3764561
--- /dev/null
+++ b/core/res/res/anim/overlay_task_fragment_close_to_right.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+   -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:fromXDelta="0"
+        android:toXDelta="100%"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:duration="517" />
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/overlay_task_fragment_close_to_top.xml b/core/res/res/anim/overlay_task_fragment_close_to_top.xml
new file mode 100644
index 0000000..a8bfbbd
--- /dev/null
+++ b/core/res/res/anim/overlay_task_fragment_close_to_top.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+   -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:fromYDelta="0"
+        android:toYDelta="-100%"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:duration="517" />
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/overlay_task_fragment_open_from_bottom.xml b/core/res/res/anim/overlay_task_fragment_open_from_bottom.xml
new file mode 100644
index 0000000..1d1223f
--- /dev/null
+++ b/core/res/res/anim/overlay_task_fragment_open_from_bottom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+   -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:fromYDelta="100%"
+        android:toYDelta="0"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:duration="517" />
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/overlay_task_fragment_open_from_left.xml b/core/res/res/anim/overlay_task_fragment_open_from_left.xml
new file mode 100644
index 0000000..5e5e080
--- /dev/null
+++ b/core/res/res/anim/overlay_task_fragment_open_from_left.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+   -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:fromXDelta="-100%"
+        android:toXDelta="0"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:duration="517" />
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/overlay_task_fragment_open_from_right.xml b/core/res/res/anim/overlay_task_fragment_open_from_right.xml
new file mode 100644
index 0000000..5674ff3
--- /dev/null
+++ b/core/res/res/anim/overlay_task_fragment_open_from_right.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+   -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:fromXDelta="100%"
+        android:toXDelta="0"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:duration="517" />
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/overlay_task_fragment_open_from_top.xml b/core/res/res/anim/overlay_task_fragment_open_from_top.xml
new file mode 100644
index 0000000..2e3dd0a
--- /dev/null
+++ b/core/res/res/anim/overlay_task_fragment_open_from_top.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+   -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:fromYDelta="-100%"
+        android:toYDelta="0"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:duration="517" />
+</set>
\ No newline at end of file
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e3f9187..f696e872 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6511,17 +6511,17 @@
     <!-- Fingerprint dangling notification title -->
     <string name="fingerprint_dangling_notification_title">Set up Fingerprint Unlock again</string>
     <!-- Fingerprint dangling notification content for only 1 fingerprint deleted -->
-    <string name="fingerprint_dangling_notification_msg_1"><xliff:g id="fingerprint">%s</xliff:g> wasn\'t working well and was deleted</string>
+    <string name="fingerprint_dangling_notification_msg_1"><xliff:g id="fingerprint">%s</xliff:g> can no longer be recognized.</string>
     <!-- Fingerprint dangling notification content for more than 1 fingerprints deleted -->
-    <string name="fingerprint_dangling_notification_msg_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> weren\'t working well and were deleted</string>
+    <string name="fingerprint_dangling_notification_msg_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> can no longer be recognized.</string>
     <!-- Fingerprint dangling notification content for only 1 fingerprint deleted and no fingerprint left-->
-    <string name="fingerprint_dangling_notification_msg_all_deleted_1"><xliff:g id="fingerprint">%s</xliff:g> wasn\'t working well and was deleted. Set it up again to unlock your phone with fingerprint.</string>
+    <string name="fingerprint_dangling_notification_msg_all_deleted_1"><xliff:g id="fingerprint">%s</xliff:g> can no longer be recognized. Set up Fingerprint Unlock again.</string>
     <!-- Fingerprint dangling notification content for more than 1 fingerprints deleted and no fingerprint left  -->
-    <string name="fingerprint_dangling_notification_msg_all_deleted_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint.</string>
+    <string name="fingerprint_dangling_notification_msg_all_deleted_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> can no longer be recognized. Set up Fingerprint Unlock again.</string>
     <!-- Face dangling notification title -->
     <string name="face_dangling_notification_title">Set up Face Unlock again</string>
     <!-- Face dangling notification content -->
-    <string name="face_dangling_notification_msg">Your face model wasn\'t working well and was deleted. Set it up again to unlock your phone with face.</string>
+    <string name="face_dangling_notification_msg">Your face model can no longer be recognized. Set up Face Unlock again.</string>
     <!-- Biometric dangling notification "set up" action button -->
     <string name="biometric_dangling_notification_action_set_up">Set up</string>
     <!-- Biometric dangling notification "Not now" action button -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6e5e106..7a51abc 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1753,6 +1753,15 @@
   <java-symbol type="anim" name="task_fragment_clear_top_close_exit" />
   <java-symbol type="anim" name="task_fragment_clear_top_open_enter" />
   <java-symbol type="anim" name="task_fragment_clear_top_open_exit" />
+  <java-symbol type="anim" name="overlay_task_fragment_open_from_left" />
+  <java-symbol type="anim" name="overlay_task_fragment_open_from_top" />
+  <java-symbol type="anim" name="overlay_task_fragment_open_from_right" />
+  <java-symbol type="anim" name="overlay_task_fragment_open_from_bottom" />
+  <java-symbol type="anim" name="overlay_task_fragment_change" />
+  <java-symbol type="anim" name="overlay_task_fragment_close_to_left" />
+  <java-symbol type="anim" name="overlay_task_fragment_close_to_top" />
+  <java-symbol type="anim" name="overlay_task_fragment_close_to_right" />
+  <java-symbol type="anim" name="overlay_task_fragment_close_to_bottom" />
 
   <java-symbol type="array" name="config_autoRotationTiltTolerance" />
   <java-symbol type="array" name="config_longPressVibePattern" />
diff --git a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
index 58e5be2..4d9b591c 100644
--- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
+++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
@@ -34,6 +34,7 @@
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertEquals;
 
+import android.app.WindowConfiguration;
 import android.content.Context;
 import android.graphics.Insets;
 import android.platform.test.annotations.Presubmit;
@@ -102,6 +103,8 @@
             mViewRoot.setOnContentApplyWindowInsetsListener(
                     mock(Window.OnContentApplyWindowInsetsListener.class));
             mBackAnimationController = new ImeBackAnimationController(mViewRoot, mInsetsController);
+            mViewRoot.mContext.getResources().getConfiguration().windowConfiguration
+                    .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
 
             when(mWindowInsetsAnimationController.getHiddenStateInsets()).thenReturn(Insets.NONE);
             when(mWindowInsetsAnimationController.getShownStateInsets()).thenReturn(IME_INSETS);
@@ -156,8 +159,28 @@
         mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, 0.5f, EDGE_LEFT));
         // commit back gesture
         mBackAnimationController.onBackInvoked();
-        // verify that InsetsController#hide is called
-        verify(mInsetsController, times(1)).hide(ime());
+        // verify that InputMethodManager#notifyImeHidden is called (which is the case whenever
+        // getInputMethodManager is called from ImeBackAnimationController)
+        verify(mViewRootInsetsControllerHost, times(1)).getInputMethodManager();
+        // verify that ImeBackAnimationController does not take control over IME insets
+        verify(mInsetsController, never()).controlWindowInsetsAnimation(anyInt(), any(), any(),
+                anyBoolean(), anyLong(), any(), anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void testMultiWindowModeNotPlayingAnim() {
+        // setup ViewRoot with WINDOWING_MODE_MULTI_WINDOW
+        mViewRoot.mContext.getResources().getConfiguration().windowConfiguration.setWindowingMode(
+                WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
+        // start back gesture
+        mBackAnimationController.onBackStarted(new BackEvent(0f, 0f, 0f, EDGE_LEFT));
+        // progress back gesture
+        mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, 0.5f, EDGE_LEFT));
+        // commit back gesture
+        mBackAnimationController.onBackInvoked();
+        // verify that InputMethodManager#notifyImeHidden is called (which is the case whenever
+        // getInputMethodManager is called from ImeBackAnimationController)
+        verify(mViewRootInsetsControllerHost, times(1)).getInputMethodManager();
         // verify that ImeBackAnimationController does not take control over IME insets
         verify(mInsetsController, never()).controlWindowInsetsAnimation(anyInt(), any(), any(),
                 anyBoolean(), anyLong(), any(), anyInt(), anyBoolean());
@@ -277,9 +300,9 @@
 
             // commit back gesture
             mBackAnimationController.onBackInvoked();
-
-            // verify that InsetsController#hide is called
-            verify(mInsetsController, times(1)).hide(ime());
+            // verify that InputMethodManager#notifyImeHidden is called (which is the case whenever
+            // getInputMethodManager is called from ImeBackAnimationController)
+            verify(mViewRootInsetsControllerHost, times(1)).getInputMethodManager();
         });
     }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index d0e49d8..eb1fc23 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -20,8 +20,10 @@
 
 import static androidx.window.extensions.embedding.DividerPresenter.getBoundsOffsetForDivider;
 import static androidx.window.extensions.embedding.SplitAttributesHelper.isReversedLayout;
+import static androidx.window.extensions.embedding.SplitController.TAG;
 import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
 
+import android.annotation.AnimRes;
 import android.app.Activity;
 import android.app.ActivityThread;
 import android.app.WindowConfiguration;
@@ -31,9 +33,11 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.util.Log;
 import android.util.Pair;
 import android.util.Size;
 import android.view.View;
@@ -56,6 +60,7 @@
 import androidx.window.extensions.layout.WindowLayoutComponentImpl;
 import androidx.window.extensions.layout.WindowLayoutInfo;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.window.flags.Flags;
 
@@ -125,6 +130,16 @@
     static final int RESULT_EXPAND_FAILED_NO_TF_INFO = 2;
 
     /**
+     * The key of {@link ActivityStack} alignment relative to its parent container.
+     * <p>
+     * See {@link ContainerPosition} for possible values.
+     * <p>
+     * Note that this constants must align with the definition in WM Jetpack library.
+     */
+    private static final String KEY_ACTIVITY_STACK_ALIGNMENT =
+            "androidx.window.embedding.ActivityStackAlignment";
+
+    /**
      * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer,
      * Activity, Activity, Intent)}
      */
@@ -649,14 +664,114 @@
         // TODO(b/243518738): Update to resizeTaskFragment after we migrate WCT#setRelativeBounds
         //  and WCT#setWindowingMode to take fragmentToken.
         resizeTaskFragmentIfRegistered(wct, container, relativeBounds);
-        int windowingMode = container.getTaskContainer().getWindowingModeForTaskFragment(
-                relativeBounds);
+        final TaskContainer taskContainer = container.getTaskContainer();
+        final int windowingMode = taskContainer.getWindowingModeForTaskFragment(relativeBounds);
         updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
-        // Always use default animation for standalone ActivityStack.
-        updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
+        if (container.isOverlay() && isOverlayTransitionSupported()) {
+            // Use the overlay transition for the overlay container if it's supported.
+            final TaskFragmentAnimationParams params = createOverlayAnimationParams(relativeBounds,
+                    taskContainer.getBounds(), container);
+            updateAnimationParams(wct, fragmentToken, params);
+        } else {
+            // Otherwise, fallabck to use the default animation params.
+            updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
+        }
         setTaskFragmentDimOnTask(wct, fragmentToken, dimOnTask);
     }
 
+    private static boolean isOverlayTransitionSupported() {
+        return Flags.moveAnimationOptionsToChange()
+                && Flags.activityEmbeddingOverlayPresentationFlag();
+    }
+
+    @NonNull
+    private static TaskFragmentAnimationParams createOverlayAnimationParams(
+            @NonNull Rect relativeBounds, @NonNull Rect parentContainerBounds,
+            @NonNull TaskFragmentContainer container) {
+        if (relativeBounds.isEmpty()) {
+            return TaskFragmentAnimationParams.DEFAULT;
+        }
+
+        final int positionFromOptions = container.getLaunchOptions()
+                .getInt(KEY_ACTIVITY_STACK_ALIGNMENT , -1);
+        final int position = positionFromOptions != -1 ? positionFromOptions
+                // Fallback to calculate from bounds if the info can't be retrieved from options.
+                : getOverlayPosition(relativeBounds, parentContainerBounds);
+
+        return new TaskFragmentAnimationParams.Builder()
+                .setOpenAnimationResId(getOpenAnimationResourcesId(position))
+                .setChangeAnimationResId(R.anim.overlay_task_fragment_change)
+                .setCloseAnimationResId(getCloseAnimationResourcesId(position))
+                .build();
+    }
+
+    @VisibleForTesting
+    @ContainerPosition
+    static int getOverlayPosition(
+            @NonNull Rect relativeBounds, @NonNull Rect parentContainerBounds) {
+        final Rect relativeParentBounds = new Rect(parentContainerBounds);
+        relativeParentBounds.offsetTo(0, 0);
+        final int leftMatch = (relativeParentBounds.left == relativeBounds.left) ? 1 : 0;
+        final int topMatch = (relativeParentBounds.top == relativeBounds.top) ? 1 : 0;
+        final int rightMatch = (relativeParentBounds.right == relativeBounds.right) ? 1 : 0;
+        final int bottomMatch = (relativeParentBounds.bottom == relativeBounds.bottom) ? 1 : 0;
+
+        // Flag format: {left|top|right|bottom}. Note that overlay container could be shrunk and
+        // centered, which makes only one of overlay container edge matches the parent container.
+        final int directionFlag = (leftMatch << 3) + (topMatch << 2) + (rightMatch << 1)
+                + bottomMatch;
+
+        final int position = switch (directionFlag) {
+            // Only the left edge match or only the right edge not match: should be on the left of
+            // the parent container.
+            case 0b1000, 0b1101 -> CONTAINER_POSITION_LEFT;
+            // Only the top edge match or only the bottom edge not match: should be on the top of
+            // the parent container.
+            case 0b0100, 0b1110 -> CONTAINER_POSITION_TOP;
+            // Only the right edge match or only the left edge not match: should be on the right of
+            // the parent container.
+            case 0b0010, 0b0111 -> CONTAINER_POSITION_RIGHT;
+            // Only the bottom edge match or only the top edge not match: should be on the bottom of
+            // the parent container.
+            case 0b0001, 0b1011 -> CONTAINER_POSITION_BOTTOM;
+            default -> {
+                Log.w(TAG, "Unsupported position:" + Integer.toBinaryString(directionFlag)
+                        + " fallback to treat it as right. Relative parent bounds: "
+                        + relativeParentBounds + ", relative overlay bounds:" + relativeBounds);
+                yield CONTAINER_POSITION_RIGHT;
+            }
+        };
+        return position;
+    }
+
+    @AnimRes
+    private static int getOpenAnimationResourcesId(@ContainerPosition int position) {
+        return switch (position) {
+            case CONTAINER_POSITION_LEFT -> R.anim.overlay_task_fragment_open_from_left;
+            case CONTAINER_POSITION_TOP -> R.anim.overlay_task_fragment_open_from_top;
+            case CONTAINER_POSITION_RIGHT -> R.anim.overlay_task_fragment_open_from_right;
+            case CONTAINER_POSITION_BOTTOM -> R.anim.overlay_task_fragment_open_from_bottom;
+            default -> {
+                Log.w(TAG, "Unknown position:" + position);
+                yield Resources.ID_NULL;
+            }
+        };
+    }
+
+    @AnimRes
+    private static int getCloseAnimationResourcesId(@ContainerPosition int position) {
+        return switch (position) {
+            case CONTAINER_POSITION_LEFT -> R.anim.overlay_task_fragment_close_to_left;
+            case CONTAINER_POSITION_TOP -> R.anim.overlay_task_fragment_close_to_top;
+            case CONTAINER_POSITION_RIGHT -> R.anim.overlay_task_fragment_close_to_right;
+            case CONTAINER_POSITION_BOTTOM -> R.anim.overlay_task_fragment_close_to_bottom;
+            default -> {
+                Log.w(TAG, "Unknown position:" + position);
+                yield Resources.ID_NULL;
+            }
+        };
+    }
+
     /**
      * Returns the expanded bounds if the {@code relBounds} violate minimum dimension or are not
      * fully covered by the task bounds. Otherwise, returns {@code relBounds}.
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 7a0b9a0..3257502 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -30,6 +30,11 @@
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTfContainer;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_LEFT;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT;
+import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_TOP;
+import static androidx.window.extensions.embedding.SplitPresenter.getOverlayPosition;
 import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds;
 import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
 
@@ -666,8 +671,8 @@
                 attributes.getRelativeBounds());
         verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
                 WINDOWING_MODE_MULTI_WINDOW);
-        verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
-                TaskFragmentAnimationParams.DEFAULT);
+        verify(mSplitPresenter).updateAnimationParams(eq(mTransaction), eq(token),
+                any(TaskFragmentAnimationParams.class));
         verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true);
         verify(mSplitPresenter, never()).setTaskFragmentPinned(any(),
                 any(TaskFragmentContainer.class), anyBoolean());
@@ -691,8 +696,8 @@
                 attributes.getRelativeBounds());
         verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction,
                 container, WINDOWING_MODE_MULTI_WINDOW);
-        verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
-                TaskFragmentAnimationParams.DEFAULT);
+        verify(mSplitPresenter).updateAnimationParams(eq(mTransaction), eq(token),
+                any(TaskFragmentAnimationParams.class));
         verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(),
                 any(TaskFragmentContainer.class), anyBoolean());
         verify(mSplitPresenter).setTaskFragmentPinned(mTransaction, container, true);
@@ -870,6 +875,59 @@
                 eq(overlayContainer.getTaskFragmentToken()), eq(activityToken));
     }
 
+    // TODO(b/243518738): Rewrite with TestParameter.
+    @Test
+    public void testGetOverlayPosition() {
+        assertWithMessage("It must be position left for left overlay.")
+                .that(getOverlayPosition(new Rect(
+                        TASK_BOUNDS.left,
+                        TASK_BOUNDS.top,
+                        TASK_BOUNDS.right / 2,
+                        TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT);
+        assertWithMessage("It must be position left for shrunk left overlay.")
+                .that(getOverlayPosition(new Rect(
+                        TASK_BOUNDS.left,
+                        TASK_BOUNDS.top + 20,
+                        TASK_BOUNDS.right / 2,
+                        TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT);
+        assertWithMessage("It must be position left for top overlay.")
+                .that(getOverlayPosition(new Rect(
+                        TASK_BOUNDS.left,
+                        TASK_BOUNDS.top,
+                        TASK_BOUNDS.right,
+                        TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP);
+        assertWithMessage("It must be position left for shrunk top overlay.")
+                .that(getOverlayPosition(new Rect(
+                        TASK_BOUNDS.left + 20,
+                        TASK_BOUNDS.top,
+                        TASK_BOUNDS.right - 20,
+                        TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP);
+        assertWithMessage("It must be position left for right overlay.")
+                .that(getOverlayPosition(new Rect(
+                        TASK_BOUNDS.right / 2,
+                        TASK_BOUNDS.top,
+                        TASK_BOUNDS.right,
+                        TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT);
+        assertWithMessage("It must be position left for shrunk right overlay.")
+                .that(getOverlayPosition(new Rect(
+                        TASK_BOUNDS.right / 2,
+                        TASK_BOUNDS.top + 20,
+                        TASK_BOUNDS.right,
+                        TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT);
+        assertWithMessage("It must be position left for bottom overlay.")
+                .that(getOverlayPosition(new Rect(
+                        TASK_BOUNDS.left,
+                        TASK_BOUNDS.bottom / 2,
+                        TASK_BOUNDS.right,
+                        TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM);
+        assertWithMessage("It must be position left for shrunk bottom overlay.")
+                .that(getOverlayPosition(new Rect(
+                        TASK_BOUNDS.left + 20,
+                        TASK_BOUNDS.bottom / 20,
+                        TASK_BOUNDS.right - 20,
+                        TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM);
+    }
+
     /**
      * A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded}
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 5a42817..d270d2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -265,7 +265,7 @@
         for (TransitionInfo.Change change : openingChanges) {
             final Animation animation =
                     animationProvider.get(info, change, openingWholeScreenBounds);
-            if (animation.getDuration() == 0) {
+            if (shouldUseJumpCutForAnimation(animation)) {
                 continue;
             }
             final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
@@ -290,7 +290,7 @@
             }
             final Animation animation =
                     animationProvider.get(info, change, closingWholeScreenBounds);
-            if (animation.getDuration() == 0) {
+            if (shouldUseJumpCutForAnimation(animation)) {
                 continue;
             }
             final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
@@ -444,8 +444,16 @@
             calculateParentBounds(change, boundsAnimationChange, parentBounds);
             // There are two animations in the array. The first one is for the start leash
             // (snapshot), and the second one is for the end leash (TaskFragment).
-            final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change,
-                    parentBounds);
+            final Animation[] animations =
+                    mAnimationSpec.createChangeBoundsChangeAnimations(info, change, parentBounds);
+            // Jump cut if either animation has zero for duration.
+            if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+                for (Animation animation : animations) {
+                    if (shouldUseJumpCutForAnimation(animation)) {
+                        return new ArrayList<>();
+                    }
+                }
+            }
             // Keep track as we might need to add background color for the animation.
             // Although there may be multiple change animation, record one of them is sufficient
             // because the background color will be added to the root leash for the whole animation.
@@ -492,12 +500,19 @@
                 // window without bounds change.
                 animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
             } else if (TransitionUtil.isClosingType(change.getMode())) {
-                animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
+                animation =
+                        mAnimationSpec.createChangeBoundsCloseAnimation(info, change, parentBounds);
                 shouldShowBackgroundColor = false;
             } else {
-                animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds);
+                animation =
+                        mAnimationSpec.createChangeBoundsOpenAnimation(info, change, parentBounds);
                 shouldShowBackgroundColor = false;
             }
+            if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+                if (shouldUseJumpCutForAnimation(animation)) {
+                    return new ArrayList<>();
+                }
+            }
             adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change,
                     TransitionUtil.getRootFor(change, info)));
         }
@@ -640,6 +655,12 @@
         return true;
     }
 
+    /** Whether or not to use jump cut based on the animation. */
+    @VisibleForTesting
+    static boolean shouldUseJumpCutForAnimation(@NonNull Animation animation) {
+        return animation.getDuration() == 0;
+    }
+
     /** Updates the changes to end states in {@code startTransaction} for jump cut animation. */
     private void prepareForJumpCut(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 8d49614..f49b90d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -46,7 +46,6 @@
 import com.android.wm.shell.shared.TransitionUtil;
 
 /** Animation spec for ActivityEmbedding transition. */
-// TODO(b/206557124): provide an easier way to customize animation
 class ActivityEmbeddingAnimationSpec {
 
     private static final String TAG = "ActivityEmbeddingAnimSpec";
@@ -95,8 +94,14 @@
 
     /** Animation for window that is opening in a change transition. */
     @NonNull
-    Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change,
-            @NonNull Rect parentBounds) {
+    Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo info,
+            @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
+        if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+            final Animation customAnimation = loadCustomAnimation(info, change);
+            if (customAnimation != null) {
+                return customAnimation;
+            }
+        }
         // Use end bounds for opening.
         final Rect bounds = change.getEndAbsBounds();
         final int startLeft;
@@ -123,8 +128,14 @@
 
     /** Animation for window that is closing in a change transition. */
     @NonNull
-    Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change,
-            @NonNull Rect parentBounds) {
+    Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo info,
+            @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
+        if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+            final Animation customAnimation = loadCustomAnimation(info, change);
+            if (customAnimation != null) {
+                return customAnimation;
+            }
+        }
         // Use start bounds for closing.
         final Rect bounds = change.getStartAbsBounds();
         final int endTop;
@@ -155,8 +166,17 @@
      *         the second one is for the end leash.
      */
     @NonNull
-    Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo.Change change,
-            @NonNull Rect parentBounds) {
+    Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo info,
+            @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
+        if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+            // TODO(b/293658614): Support more complicated animations that may need more than a noop
+            // animation as the start leash.
+            final Animation noopAnimation = createNoopAnimation(change);
+            final Animation customAnimation = loadCustomAnimation(info, change);
+            if (customAnimation != null) {
+                return new Animation[]{noopAnimation, customAnimation};
+            }
+        }
         // Both start bounds and end bounds are in screen coordinates. We will post translate
         // to the local coordinates in ActivityEmbeddingAnimationAdapter#onAnimationUpdate
         final Rect startBounds = change.getStartAbsBounds();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 731f75bf..55b6bd2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -21,10 +21,12 @@
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 
 import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.calculateParentBounds;
+import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.shouldUseJumpCutForAnimation;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
@@ -40,6 +42,8 @@
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
 import android.window.TransitionInfo;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -281,6 +285,18 @@
                 actualParentBounds);
     }
 
+    @Test
+    public void testShouldUseJumpCutForAnimation() {
+        final Animation noopAnimation = new AlphaAnimation(0f, 1f);
+        assertTrue("Animation without duration should use jump cut.",
+                shouldUseJumpCutForAnimation(noopAnimation));
+
+        final Animation alphaAnimation = new AlphaAnimation(0f, 1f);
+        alphaAnimation.setDuration(100);
+        assertFalse("Animation with duration should not use jump cut.",
+                shouldUseJumpCutForAnimation(alphaAnimation));
+    }
+
     @NonNull
     private static TransitionInfo.Change prepareChangeForParentBoundsCalculationTest(
             @NonNull Point endRelOffset, @NonNull Rect endAbsBounds, @NonNull Point endParentSize) {
diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp
index 1325fc1..028c97e 100644
--- a/media/tests/MediaFrameworkTest/Android.bp
+++ b/media/tests/MediaFrameworkTest/Android.bp
@@ -24,6 +24,7 @@
         "flag-junit",
         "testng",
         "truth",
+        "collector-device-lib-platform",
     ],
     jni_libs: [
         "libdexmakerjvmtiagent",
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
index 727c61c..a7e0464 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
@@ -498,7 +498,7 @@
         ): Job =
             coroutineScope.launch {
                 val wifiManager = context.getSystemService(WifiManager::class.java) ?: return@launch
-                if (wifiManager.queryWepAllowed()) {
+                if (wifiManager.isWepSupported == true && wifiManager.queryWepAllowed()) {
                     onAllowed()
                 } else {
                     val intent = Intent(Intent.ACTION_MAIN).apply {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 05eb044..861c405 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -203,6 +203,12 @@
 
     private static final String NULL_VALUE = "null";
 
+    // TOBO(b/312444587): remove after Test Mission 2.
+    // Bulk sync names
+    private static final String BULK_SYNC_MARKER = "aconfigd_marker/bulk_synced";
+    private static final String BULK_SYNC_TRIGGER_COUNTER =
+        "core_experiments_team_internal/BulkSyncTriggerCounterFlag__bulk_sync_trigger_counter";
+
     private static final ArraySet<String> sSystemPackages = new ArraySet<>();
 
     private final Object mWriteLock = new Object();
@@ -409,8 +415,7 @@
                     }
                 }
                 // TOBO(b/312444587): remove the comparison logic after Test Mission 2.
-                if (mSettings.get("aconfigd_marker/bulk_synced").value.equals("true")
-                        && requests == null) {
+                if (requests == null) {
                     Map<String, AconfigdFlagInfo> aconfigdFlagMap =
                             AconfigdJavaUtils.listFlagsValueInNewStorage(localSocket);
                     compareFlagValueInNewStorage(
@@ -534,7 +539,7 @@
             return null;
         }
         AconfigdFlagInfo flag = flagInfoDefault.get(fullFlagName);
-        if (flag == null) {
+        if (flag == null || !namespace.equals(flag.getNamespace())) {
             return null;
         }
 
@@ -553,15 +558,33 @@
     public ProtoOutputStream handleBulkSyncToNewStorage(
             Map<String, AconfigdFlagInfo> aconfigFlagMap) {
         // get marker or add marker if it does not exist
-        final String bulkSyncMarkerName = new String("aconfigd_marker/bulk_synced");
-        Setting markerSetting = mSettings.get(bulkSyncMarkerName);
+        Setting markerSetting = mSettings.get(BULK_SYNC_MARKER);
+        int localCounter = 0;
         if (markerSetting == null) {
-            markerSetting = new Setting(bulkSyncMarkerName, "false", false, "aconfig", "aconfig");
-            mSettings.put(bulkSyncMarkerName, markerSetting);
+            markerSetting = new Setting(BULK_SYNC_MARKER, "0", false, "aconfig", "aconfig");
+            mSettings.put(BULK_SYNC_MARKER, markerSetting);
+        }
+        try {
+            localCounter = Integer.parseInt(markerSetting.value);
+        } catch(NumberFormatException e) {
+            // reset local counter
+            markerSetting.value = "0";
         }
 
         if (enableAconfigStorageDaemon()) {
-            if (markerSetting.value.equals("true")) {
+            Setting bulkSyncCounter = mSettings.get(BULK_SYNC_TRIGGER_COUNTER);
+            int serverCounter = 0;
+            if (bulkSyncCounter != null) {
+                try {
+                    serverCounter = Integer.parseInt(bulkSyncCounter.value);
+                } catch (NumberFormatException e) {
+                    // reset the local value of server counter
+                    bulkSyncCounter.value = "0";
+                }
+            }
+
+            boolean shouldSync = localCounter < serverCounter;
+            if (!shouldSync) {
                 // CASE 1, flag is on, bulk sync marker true, nothing to do
                 return null;
             } else {
@@ -600,20 +623,12 @@
                 }
 
                 // mark sync has been done
-                markerSetting.value = "true";
+                markerSetting.value = String.valueOf(serverCounter);
                 scheduleWriteIfNeededLocked();
                 return requests;
             }
         } else {
-            if (markerSetting.value.equals("true")) {
-                // CASE 3, flag is off, bulk sync marker true, clear the marker
-                markerSetting.value = "false";
-                scheduleWriteIfNeededLocked();
-                return null;
-            } else {
-                // CASE 4, flag is off, bulk sync marker false, nothing to do
-                return null;
-            }
+            return null;
         }
     }
 
@@ -692,6 +707,7 @@
                                     .setFlagName(flag.getName())
                                     .setDefaultFlagValue(flagValue)
                                     .setIsReadWrite(isReadWrite)
+                                    .setNamespace(flag.getNamespace())
                                     .build());
                 }
             }
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 94aeb9b..4b4ced3 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -151,12 +151,14 @@
                                                 .setFlagName("flag1")
                                                 .setDefaultFlagValue("false")
                                                 .setIsReadWrite(true)
+                                                .setNamespace("test_namespace")
                                                 .build();
         AconfigdFlagInfo flag2 = AconfigdFlagInfo.newBuilder()
                                                 .setPackageName("com.android.flags")
                                                 .setFlagName("flag2")
                                                 .setDefaultFlagValue("true")
                                                 .setIsReadWrite(false)
+                                                .setNamespace("test_namespace")
                                                 .build();
         Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>();
 
@@ -1018,12 +1020,17 @@
                         .setFlagName("flag1")
                         .setDefaultFlagValue("false")
                         .setIsReadWrite(true)
+                        .setNamespace("test_namespace")
                         .build();
 
         flagInfoDefault.put(flag1.getFullFlagName(), flag1);
 
-        // server override
+        // not the right namespace
+        assertNull(
+                settingsState.getFlagOverrideToSync(
+                        "some_namespace/com.android.flags.flag1", "true", flagInfoDefault));
 
+        // server override
         settingsState.getFlagOverrideToSync(
                 "test_namespace/com.android.flags.flag1", "true", flagInfoDefault);
         assertEquals("com.android.flags", flag1.getPackageName());
@@ -1079,21 +1086,45 @@
                         .setIsReadWrite(false)
                         .build());
 
+        String bulkSyncMarker = "aconfigd_marker/bulk_synced";
+        String bulkSyncCounter =
+                "core_experiments_team_internal/" +
+                "BulkSyncTriggerCounterFlag__bulk_sync_trigger_counter";
+
         synchronized (lock) {
-            settingsState.insertSettingLocked(
-                    "aconfigd_marker/bulk_synced", "false", null, false, "aconfig");
+            settingsState.insertSettingLocked(bulkSyncMarker, "0", null, false, "aconfig");
+            settingsState.insertSettingLocked(bulkSyncCounter, "1", null, false,
+                    "com.google.android.platform.core_experiments_team_internal");
 
             // first bulk sync
             ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage(flags);
             assertTrue(requests != null);
             String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
-            assertEquals("true", value);
+            assertEquals("1", value);
 
             // send time should no longer bulk sync
             requests = settingsState.handleBulkSyncToNewStorage(flags);
-            assertTrue(requests == null);
+            assertNull(requests);
             value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
-            assertEquals("true", value);
+            assertEquals("1", value);
+
+            // won't sync if the marker is string
+            settingsState.insertSettingLocked(bulkSyncMarker, "true", null, false, "aconfig");
+            settingsState.insertSettingLocked(bulkSyncCounter, "0", null, false,
+                    "com.google.android.platform.core_experiments_team_internal");
+            requests = settingsState.handleBulkSyncToNewStorage(flags);
+            assertNull(requests);
+            value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
+            assertEquals("0", value);
+
+            // won't sync if the marker and counter value are the same
+            settingsState.insertSettingLocked(bulkSyncMarker, "1", null, false, "aconfig");
+            settingsState.insertSettingLocked(bulkSyncCounter, "1", null, false,
+                    "com.google.android.platform.core_experiments_team_internal");
+            requests = settingsState.handleBulkSyncToNewStorage(flags);
+            assertNull(requests);
+            value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
+            assertEquals("1", value);
         }
     }
 
@@ -1107,21 +1138,34 @@
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
 
         Map<String, AconfigdFlagInfo> flags = new HashMap<>();
+        String bulkSyncMarker = "aconfigd_marker/bulk_synced";
+        String bulkSyncCounter =
+                "core_experiments_team_internal/" +
+                "BulkSyncTriggerCounterFlag__bulk_sync_trigger_counter";
         synchronized (lock) {
             settingsState.insertSettingLocked("aconfigd_marker/bulk_synced",
                     "true", null, false, "aconfig");
 
             // when aconfigd is off, should change the marker to false
             ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage(flags);
-            assertTrue(requests == null);
+            assertNull(requests);
             String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
-            assertEquals("false", value);
+            assertEquals("0", value);
 
             // marker started with false value, after call, it should remain false
             requests = settingsState.handleBulkSyncToNewStorage(flags);
-            assertTrue(requests == null);
+            assertNull(requests);
             value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
-            assertEquals("false", value);
+            assertEquals("0", value);
+
+            // won't sync
+            settingsState.insertSettingLocked(bulkSyncMarker, "0", null, false, "aconfig");
+            settingsState.insertSettingLocked(bulkSyncCounter, "1", null, false,
+                    "com.google.android.platform.core_experiments_team_internal");
+            requests = settingsState.handleBulkSyncToNewStorage(flags);
+            assertNull(requests);
+            value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
+            assertEquals("0", value);
         }
     }
 
@@ -1164,6 +1208,7 @@
                         .setFlagName("flag1")
                         .setDefaultFlagValue("false")
                         .setIsReadWrite(true)
+                        .setNamespace("test_namespace")
                         .build();
         flagInfoDefault.put(flag1.getFullFlagName(), flag1);
 
@@ -1186,6 +1231,7 @@
                         .setFlagName("flag2")
                         .setDefaultFlagValue("false")
                         .setIsReadWrite(true)
+                        .setNamespace("test_namespace")
                         .build();
         flagInfoDefault.put(flag2.getFullFlagName(), flag2);
         synchronized (lock) {
@@ -1207,6 +1253,7 @@
                         .setFlagName("flag3")
                         .setDefaultFlagValue("false")
                         .setIsReadWrite(false)
+                        .setNamespace("test_namespace")
                         .build();
         flagInfoDefault.put(flag3.getFullFlagName(), flag3);
         synchronized (lock) {
diff --git a/packages/SystemUI/res/color/connected_network_primary_color.xml b/packages/SystemUI/res/color/connected_network_primary_color.xml
new file mode 100644
index 0000000..f173c8d
--- /dev/null
+++ b/packages/SystemUI/res/color/connected_network_primary_color.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ Copyright (C) 2024 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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:color="?androidprv:attr/materialColorOnPrimaryContainer" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml
index 250188b..fab2d8d 100644
--- a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml
+++ b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml
@@ -16,10 +16,11 @@
   -->
 
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:color="?android:attr/colorControlHighlight">
     <item>
         <shape android:shape="rectangle">
-            <solid android:color="@color/settingslib_state_on_color"/>
+            <solid android:color="?androidprv:attr/materialColorPrimaryContainer"/>
             <corners android:radius="@dimen/settingslib_switch_bar_radius"/>
         </shape>
     </item>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_on.xml b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml
index 5566ea3..e316a93 100644
--- a/packages/SystemUI/res/drawable/settingslib_thumb_on.xml
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml
@@ -15,7 +15,8 @@
   limitations under the License.
   -->
 
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
     <item
         android:top="@dimen/settingslib_switch_thumb_margin"
         android:bottom="@dimen/settingslib_switch_thumb_margin">
@@ -23,7 +24,7 @@
             <size
                 android:height="@dimen/settingslib_switch_thumb_size"
                 android:width="@dimen/settingslib_switch_thumb_size"/>
-            <solid android:color="@color/settingslib_state_on_color"/>
+            <solid android:color="?androidprv:attr/materialColorOnPrimary"/>
         </shape>
     </item>
 </layer-list>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_on_background.xml b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml
index 1d9dacd..e2e6468 100644
--- a/packages/SystemUI/res/drawable/settingslib_track_on_background.xml
+++ b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml
@@ -16,11 +16,12 @@
   -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="rectangle"
     android:width="@dimen/settingslib_switch_track_width"
     android:height="@dimen/settingslib_switch_track_height">
     <padding android:left="@dimen/settingslib_switch_thumb_margin"
              android:right="@dimen/settingslib_switch_thumb_margin"/>
-    <solid android:color="@color/settingslib_track_on_color"/>
+    <solid android:color="?androidprv:attr/materialColorPrimary"/>
     <corners android:radius="@dimen/settingslib_switch_track_radius"/>
 </shape>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index d377e01..21f1cfb 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -104,10 +104,6 @@
 
     <color name="people_tile_background">@color/material_dynamic_secondary20</color>
 
-    <!-- Internet Dialog -->
-    <color name="connected_network_primary_color">@color/material_dynamic_primary80</color>
-    <color name="connected_network_secondary_color">@color/material_dynamic_secondary80</color>
-
     <!-- Keyboard shortcut helper dialog -->
     <color name="ksh_key_item_color">@*android:color/system_on_surface_variant_dark</color>
 </resources>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 546bf1c..e9dd039f3 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -60,18 +60,6 @@
         <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
     </style>
 
-    <style name="TextAppearance.InternetDialog.Active">
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:textSize">16sp</item>
-        <item name="android:textColor">@color/material_dynamic_primary80</item>
-        <item name="android:textDirection">locale</item>
-    </style>
-
-    <style name="TextAppearance.InternetDialog.Secondary.Active">
-        <item name="android:textSize">14sp</item>
-        <item name="android:textColor">@color/material_dynamic_secondary80</item>
-    </style>
-
     <style name="ShortcutHelperTheme" parent="@style/ShortcutHelperThemeCommon">
         <item name="android:windowLightNavigationBar">false</item>
     </style>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index b3d3021..0350cd7 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -236,11 +236,8 @@
     <!-- Internet Dialog -->
     <!-- Material next state on color-->
     <color name="settingslib_state_on_color">@color/settingslib_state_on</color>
-    <!-- Material next track on color-->
-    <color name="settingslib_track_on_color">@color/settingslib_track_on</color>
     <!-- Material next track off color-->
     <color name="settingslib_track_off_color">@color/settingslib_track_off</color>
-    <color name="connected_network_primary_color">#191C18</color>
     <color name="connected_network_secondary_color">#41493D</color>
 
     <color name="dream_overlay_camera_mic_off_dot_color">#FCBE03</color>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 73b7586..7475eb2 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1315,7 +1315,7 @@
         <item name="android:background">?android:attr/selectableItemBackground</item>
     </style>
 
-    <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault">
+    <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault.DayNight">
         <item name="android:switchMinWidth">@dimen/settingslib_min_switch_width</item>
     </style>
 
@@ -1358,6 +1358,7 @@
 
     <style name="InternetDialog.NetworkTitle.Active">
         <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Active</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
     </style>
 
     <style name="InternetDialog.NetworkSummary">
@@ -1370,18 +1371,19 @@
     <style name="InternetDialog.NetworkSummary.Active">
         <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Secondary.Active
         </item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
     </style>
 
     <style name="TextAppearance.InternetDialog">
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:textSize">16sp</item>
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
         <item name="android:textDirection">locale</item>
     </style>
 
     <style name="TextAppearance.InternetDialog.Secondary">
         <item name="android:textSize">14sp</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
     </style>
 
     <style name="TextAppearance.InternetDialog.Active"/>
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
index fec6ff1..0458f53 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
@@ -28,6 +28,9 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import java.util.concurrent.CopyOnWriteArrayList
+import java.util.concurrent.atomic.AtomicInteger
+
+private const val INVALID_ROTATION = -1
 
 /**
  * Allows to subscribe to rotation changes. Updates are provided for the display associated to
@@ -45,7 +48,7 @@
     private val listeners = CopyOnWriteArrayList<RotationListener>()
 
     private val displayListener = RotationDisplayListener()
-    private var lastRotation: Int? = null
+    private val lastRotation = AtomicInteger(INVALID_ROTATION)
 
     override fun addCallback(listener: RotationListener) {
         bgHandler.post {
@@ -61,7 +64,7 @@
             listeners -= listener
             if (listeners.isEmpty()) {
                 unsubscribeToRotation()
-                lastRotation = null
+                lastRotation.set(INVALID_ROTATION)
             }
         }
     }
@@ -100,9 +103,8 @@
 
                 if (displayId == display.displayId) {
                     val currentRotation = display.rotation
-                    if (lastRotation == null || lastRotation != currentRotation) {
+                    if (lastRotation.compareAndSet(lastRotation.get(), currentRotation)) {
                         listeners.forEach { it.onRotationChanged(currentRotation) }
-                        lastRotation = currentRotation
                     }
                 }
             } finally {
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 18a9986..886857c 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -1079,6 +1079,21 @@
         }
 
         @Override // Binder call
+        public void finishSelfOneway(IBinder token, boolean immediate) {
+            // Requires no permission, called by Dream from an arbitrary process.
+            if (token == null) {
+                throw new IllegalArgumentException("token must not be null");
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                finishSelfInternal(token, immediate);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
         public void startDozing(
                 IBinder token, int screenState, @Display.StateReason int reason,
                 int screenBrightness) {
@@ -1096,6 +1111,23 @@
         }
 
         @Override // Binder call
+        public void startDozingOneway(
+                IBinder token, int screenState, @Display.StateReason int reason,
+                int screenBrightness) {
+            // Requires no permission, called by Dream from an arbitrary process.
+            if (token == null) {
+                throw new IllegalArgumentException("token must not be null");
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                startDozingInternal(token, screenState, reason, screenBrightness);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
         public void stopDozing(IBinder token) {
             // Requires no permission, called by Dream from an arbitrary process.
             if (token == null) {
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index aab3374..356bc40 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -53,10 +53,10 @@
 import java.util.Objects;
 
 /**
- * The default implementation of {@link ImeVisibilityApplier} used in
- * {@link InputMethodManagerService}.
+ * A stateless helper class for IME visibility operations like show/hide and update Z-ordering
+ * relative to the IME targeted window.
  */
-final class DefaultImeVisibilityApplier implements ImeVisibilityApplier {
+final class DefaultImeVisibilityApplier {
 
     private static final String TAG = "DefaultImeVisibilityApplier";
 
@@ -75,9 +75,18 @@
         mImeTargetVisibilityPolicy = LocalServices.getService(ImeTargetVisibilityPolicy.class);
     }
 
+    /**
+     * Performs showing IME on top of the given window.
+     *
+     * @param showInputToken a token that represents the requester to show IME
+     * @param statsToken     the token tracking the current IME request
+     * @param resultReceiver if non-null, this will be called back to the caller when
+     *                       it has processed request to tell what it has done
+     * @param reason         yhe reason for requesting to show IME
+     * @param userId         the target user when performing show IME
+     */
     @GuardedBy("ImfLock.class")
-    @Override
-    public void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
+    void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
             @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason, @UserIdInt int userId) {
         final var bindingController = mService.getInputMethodBindingController(userId);
@@ -105,9 +114,18 @@
         }
     }
 
+    /**
+     * Performs hiding IME to the given window
+     *
+     * @param hideInputToken a token that represents the requester to hide IME
+     * @param statsToken     the token tracking the current IME request
+     * @param resultReceiver if non-null, this will be called back to the caller when
+     *                       it has processed request to tell what it has done
+     * @param reason         the reason for requesting to hide IME
+     * @param userId         the target user when performing hide IME
+     */
     @GuardedBy("ImfLock.class")
-    @Override
-    public void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
+    void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason,
             @UserIdInt int userId) {
         final var bindingController = mService.getInputMethodBindingController(userId);
@@ -139,9 +157,18 @@
         }
     }
 
+    /**
+     * Applies the IME visibility from {@link android.inputmethodservice.InputMethodService} with
+     * according to the given visibility state.
+     *
+     * @param windowToken the token of a window for applying the IME visibility
+     * @param statsToken  the token tracking the current IME request
+     * @param state       the new IME visibility state for the applier to handle
+     * @param reason      one of {@link SoftInputShowHideReason}
+     * @param userId      the target user when applying the IME visibility state
+     */
     @GuardedBy("ImfLock.class")
-    @Override
-    public void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+    void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
             @ImeVisibilityStateComputer.VisibilityState int state,
             @SoftInputShowHideReason int reason, @UserIdInt int userId) {
         final var bindingController = mService.getInputMethodBindingController(userId);
@@ -211,9 +238,16 @@
         }
     }
 
+    /**
+     * Shows the IME screenshot and attach it to the given IME target window.
+     *
+     * @param imeTarget   the token of a window to show the IME screenshot
+     * @param displayId   the unique id to identify the display
+     * @param userId      the target user when when showing the IME screenshot
+     * @return {@code true} if success, {@code false} otherwise
+     */
     @GuardedBy("ImfLock.class")
-    @Override
-    public boolean showImeScreenshot(@NonNull IBinder imeTarget, int displayId,
+    boolean showImeScreenshot(@NonNull IBinder imeTarget, int displayId,
             @UserIdInt int userId) {
         if (mImeTargetVisibilityPolicy.showImeScreenshot(imeTarget, displayId)) {
             mService.onShowHideSoftInputRequested(false /* show */, imeTarget,
@@ -223,9 +257,15 @@
         return false;
     }
 
+    /**
+     * Removes the IME screenshot on the given display.
+     *
+     * @param displayId the target display of showing IME screenshot
+     * @param userId    the target user of showing IME screenshot
+     * @return {@code true} if success, {@code false} otherwise
+     */
     @GuardedBy("ImfLock.class")
-    @Override
-    public boolean removeImeScreenshot(int displayId, @UserIdInt int userId) {
+    boolean removeImeScreenshot(int displayId, @UserIdInt int userId) {
         final var userData = mService.getUserData(userId);
         if (mImeTargetVisibilityPolicy.removeImeScreenshot(displayId)) {
             mService.onShowHideSoftInputRequested(false /* show */,
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
deleted file mode 100644
index b693a66..0000000
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 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.server.inputmethod;
-
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.os.IBinder;
-import android.os.ResultReceiver;
-import android.view.inputmethod.ImeTracker;
-import android.view.inputmethod.InputMethod;
-
-import com.android.internal.inputmethod.SoftInputShowHideReason;
-
-/**
- * Interface for IME visibility operations like show/hide and update Z-ordering relative to the IME
- * targeted window.
- */
-interface ImeVisibilityApplier {
-    /**
-     * Performs showing IME on top of the given window.
-     *
-     * @param showInputToken a token that represents the requester to show IME
-     * @param statsToken     the token tracking the current IME request
-     * @param resultReceiver if non-null, this will be called back to the caller when
-     *                       it has processed request to tell what it has done
-     * @param reason         yhe reason for requesting to show IME
-     * @param userId         the target user when performing show IME
-     */
-    default void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
-            @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason, @UserIdInt int userId) {
-    }
-
-    /**
-     * Performs hiding IME to the given window
-     *
-     * @param hideInputToken a token that represents the requester to hide IME
-     * @param statsToken     the token tracking the current IME request
-     * @param resultReceiver if non-null, this will be called back to the caller when
-     *                       it has processed request to tell what it has done
-     * @param reason         the reason for requesting to hide IME
-     * @param userId         the target user when performing hide IME
-     */
-    default void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason,
-            @UserIdInt int userId) {
-    }
-
-    /**
-     * Applies the IME visibility from {@link android.inputmethodservice.InputMethodService} with
-     * according to the given visibility state.
-     *
-     * @param windowToken the token of a window for applying the IME visibility
-     * @param statsToken  the token tracking the current IME request
-     * @param state       the new IME visibility state for the applier to handle
-     * @param reason      the reason why the input window is visible or hidden
-     * @param userId      the target user when applying the IME visibility state
-     */
-    default void applyImeVisibility(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
-            @ImeVisibilityStateComputer.VisibilityState int state,
-            @SoftInputShowHideReason int reason, @UserIdInt int userId) {
-    }
-
-    /**
-     * Updates the IME Z-ordering relative to the given window.
-     *
-     * This used to adjust the IME relative layer of the window during
-     * {@link InputMethodManagerService} is in switching IME clients.
-     *
-     * @param windowToken the token of a window to update the Z-ordering relative to the IME
-     */
-    default void updateImeLayeringByTarget(IBinder windowToken) {
-        // TODO: add a method in WindowManagerInternal to call DC#updateImeInputAndControlTarget
-        //  here to end up updating IME layering after IMMS#attachNewInputLocked called.
-    }
-
-    /**
-     * Shows the IME screenshot and attach it to the given IME target window.
-     *
-     * @param windowToken the token of a window to show the IME screenshot
-     * @param displayId   the unique id to identify the display
-     * @param userId      the target user when when showing the IME screenshot
-     * @return {@code true} if success, {@code false} otherwise
-     */
-    default boolean showImeScreenshot(@NonNull IBinder windowToken, int displayId,
-            @UserIdInt int userId) {
-        return false;
-    }
-
-    /**
-     * Removes the IME screenshot on the given display.
-     *
-     * @param displayId the target display of showing IME screenshot
-     * @param userId    the target user of showing IME screenshot
-     * @return {@code true} if success, {@code false} otherwise
-     */
-    default boolean removeImeScreenshot(int displayId, @UserIdInt int userId) {
-        return false;
-    }
-}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 56ac760..1e76324 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4045,7 +4045,7 @@
             if (!calledWithValidTokenLocked(token, userId)) {
                 return;
             }
-            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             final InputMethodInfo imi = settings.getMethodMap().get(id);
             if (imi == null || !canCallerAccessInputMethod(
                     imi.getPackageName(), callingUid, userId, settings)) {
@@ -4063,7 +4063,7 @@
             if (!calledWithValidTokenLocked(token, userId)) {
                 return;
             }
-            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             final InputMethodInfo imi = settings.getMethodMap().get(id);
             if (imi == null || !canCallerAccessInputMethod(
                     imi.getPackageName(), callingUid, userId, settings)) {
@@ -4824,11 +4824,10 @@
         }
     }
 
+    @GuardedBy("ImfLock.class")
     @VisibleForTesting
-    ImeVisibilityApplier getVisibilityApplier() {
-        synchronized (ImfLock.class) {
-            return mVisibilityApplier;
-        }
+    DefaultImeVisibilityApplier getVisibilityApplierLocked() {
+        return mVisibilityApplier;
     }
 
     void onApplyImeVisibilityFromComputer(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6abd488..76fd57b 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2158,6 +2158,12 @@
                 // Use Task#setBoundsUnchecked to skip checking windowing mode as the windowing mode
                 // will be updated later after this is collected in transition.
                 rootTask.setBoundsUnchecked(taskFragment.getBounds());
+                // The exit-PIP activity resumes early for seamless transition. In certain
+                // scenarios, this introduces unintended addition to recents. To address this,
+                // we mark the root task for automatic removal from recents. This ensures that
+                // after the pinned activity reparents to its original task, the root task is
+                // automatically removed from the recents list.
+                rootTask.autoRemoveRecents = true;
 
                 // Move the last recents animation transaction from original task to the new one.
                 if (task.mLastRecentsAnimationTransaction != null) {
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index f8e196e..33ea9b4 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -74,9 +74,8 @@
     @Before
     public void setUp() throws RemoteException {
         super.setUp();
-        mVisibilityApplier =
-                (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier();
         synchronized (ImfLock.class) {
+            mVisibilityApplier = mInputMethodManagerService.getVisibilityApplierLocked();
             mUserId = mInputMethodManagerService.getCurrentImeUserIdLocked();
             mInputMethodManagerService.setAttachedClientForTesting(requireNonNull(
                     mInputMethodManagerService.getClientStateLocked(mMockInputMethodClient)));
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index a22cacb..337d5c1 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -58,8 +58,8 @@
 import org.mockito.ArgumentCaptor;
 
 /**
- * Test the behavior of {@link ImeVisibilityStateComputer} and {@link ImeVisibilityApplier} when
- * requesting the IME visibility.
+ * Test the behavior of {@link ImeVisibilityStateComputer} and {@link DefaultImeVisibilityApplier}
+ * when requesting the IME visibility.
  *
  * <p> Build/Install/Run:
  * atest FrameworksInputMethodSystemServerTests:ImeVisibilityStateComputerTest
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index eb79118..3078df0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -392,6 +392,8 @@
         assertEquals(newPipTask, mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask());
         assertNotEquals(newPipTask, activity1.getTask());
         assertFalse("Created PiP task must not be in recents", newPipTask.inRecents);
+        assertThat(newPipTask.autoRemoveRecents).isTrue();
+        assertThat(activity1.getTask().autoRemoveRecents).isFalse();
     }
 
     /**
@@ -427,6 +429,7 @@
         bounds.scale(0.5f);
         task.setBounds(bounds);
         assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertThat(task.autoRemoveRecents).isFalse();
     }
 
     /**
@@ -451,6 +454,7 @@
         // Ensure a task has moved over.
         ensureTaskPlacement(task, activity);
         assertTrue(task.inPinnedWindowingMode());
+        assertThat(task.autoRemoveRecents).isFalse();
     }
 
     /**
@@ -480,6 +484,8 @@
         ensureTaskPlacement(fullscreenTask, secondActivity);
         assertTrue(pinnedRootTask.inPinnedWindowingMode());
         assertEquals(WINDOWING_MODE_FULLSCREEN, fullscreenTask.getWindowingMode());
+        assertThat(pinnedRootTask.autoRemoveRecents).isTrue();
+        assertThat(secondActivity.getTask().autoRemoveRecents).isFalse();
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index e01cea3..ef0aa9e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -42,6 +42,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
 import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK;
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
@@ -222,6 +223,27 @@
     }
 
     @Test
+    public void testReparentPinnedActivityBackToOriginalTask() {
+        final ActivityRecord activityMain = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Task originalTask = activityMain.getTask();
+        final ActivityRecord activityPip = new ActivityBuilder(mAtm).setTask(originalTask).build();
+        activityPip.setState(RESUMED, "test");
+        mAtm.mRootWindowContainer.moveActivityToPinnedRootTask(activityPip,
+                null /* launchIntoPipHostActivity */, "test");
+        final Task pinnedActivityTask = activityPip.getTask();
+
+        // Simulate pinnedActivityTask unintentionally added to recent during top activity resume.
+        mAtm.getRecentTasks().getRawTasks().add(pinnedActivityTask);
+
+        // Reparent the activity back to its original task when exiting PIP mode.
+        pinnedActivityTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+        assertThat(activityPip.getTask()).isEqualTo(originalTask);
+        assertThat(originalTask.autoRemoveRecents).isFalse();
+        assertThat(mAtm.getRecentTasks().getRawTasks()).containsExactly(originalTask);
+    }
+
+    @Test
     public void testReparent_BetweenDisplays() {
         // Create first task on primary display.
         final Task rootTask1 = createTask(mDisplayContent);