Merge changes from topic "tc_change_23042206_37" into udc-dev

* changes:
  [automerged blank] Import translations. DO NOT MERGE ANYWHERE 2p: 4981708bfb
  Import translations. DO NOT MERGE ANYWHERE
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7b68357..27f5545 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9599,6 +9599,7 @@
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             } catch (IllegalArgumentException ex) {
+                Log.e(TAG, "IllegalArgumentException checking isPackageSuspended", ex);
                 throw new NameNotFoundException(packageName);
             }
         }
diff --git a/core/java/android/app/admin/LockTaskPolicy.java b/core/java/android/app/admin/LockTaskPolicy.java
index f5d1cb4..b671d57 100644
--- a/core/java/android/app/admin/LockTaskPolicy.java
+++ b/core/java/android/app/admin/LockTaskPolicy.java
@@ -38,6 +38,7 @@
     /**
      * @hide
      */
+    // We default on the power button menu, in order to be consistent with pre-P behaviour
     public static final int DEFAULT_LOCK_TASK_FLAG =
             DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
 
@@ -72,18 +73,28 @@
     /**
      * @hide
      */
-    public LockTaskPolicy(@NonNull Set<String> packages) {
-        Objects.requireNonNull(packages);
-        mPackages.addAll(packages);
+    public LockTaskPolicy(@Nullable Set<String> packages) {
+        if (packages != null) {
+            mPackages.addAll(packages);
+        }
         setValue(this);
     }
 
     /**
      * @hide
      */
-    public LockTaskPolicy(@NonNull Set<String> packages, int flags) {
-        Objects.requireNonNull(packages);
-        mPackages = new HashSet<>(packages);
+    public LockTaskPolicy(int flags) {
+        mFlags = flags;
+        setValue(this);
+    }
+
+    /**
+     * @hide
+     */
+    public LockTaskPolicy(@Nullable Set<String> packages, int flags) {
+        if (packages != null) {
+            mPackages.addAll(packages);
+        }
         mFlags = flags;
         setValue(this);
     }
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index ed6a88f..70b72c8 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -304,7 +304,9 @@
                 return;
             }
             case DO_SET_STYLUS_WINDOW_IDLE_TIMEOUT: {
-                inputMethod.setStylusWindowIdleTimeoutForTest((long) msg.obj);
+                if (isValid(inputMethod, target, "DO_SET_STYLUS_WINDOW_IDLE_TIMEOUT")) {
+                    inputMethod.setStylusWindowIdleTimeoutForTest((long) msg.obj);
+                }
                 return;
             }
         }
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index efed688..365f913 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
 import static android.app.ActivityManager.procStateToString;
 import static android.content.pm.PackageManager.GET_SIGNATURES;
 
@@ -805,6 +806,9 @@
     /** @hide */
     public static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(
             int procState, @ProcessCapability int capability) {
+        if (procState == PROCESS_STATE_UNKNOWN) {
+            return false;
+        }
         return procState <= FOREGROUND_THRESHOLD_STATE
                 || (capability & ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0;
     }
@@ -832,6 +836,9 @@
     /** @hide */
     public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState,
             @ProcessCapability int capabilities) {
+        if (procState == PROCESS_STATE_UNKNOWN) {
+            return false;
+        }
         return procState <= FOREGROUND_THRESHOLD_STATE
                 // This is meant to be a user-initiated job, and therefore gets similar network
                 // access to FGS.
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 7383e63..9f9c222 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -493,7 +493,7 @@
          * @hide
          */
         @TestApi
-        public static final int RESOURCES_SDK_INT = SDK_INT;
+        public static final int RESOURCES_SDK_INT = SDK_INT + ACTIVE_CODENAMES.length;
 
         /**
          * The current lowest supported value of app target SDK. Applications targeting
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 24c96ea..91c350a 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -1334,13 +1334,7 @@
     @Override
     public void destroy() {
         synchronized (mLock) {
-            if (mAvailability == STATE_KEYPHRASE_ENROLLED) {
-                try {
-                    stopRecognition();
-                } catch (Exception e) {
-                    Log.i(TAG, "failed to stopRecognition in destroy", e);
-                }
-            }
+            detachSessionLocked();
 
             mAvailability = STATE_INVALID;
             mIsAvailabilityOverriddenByTestApi = false;
@@ -1349,6 +1343,17 @@
         super.destroy();
     }
 
+    private void detachSessionLocked() {
+        try {
+            if (DBG) Slog.d(TAG, "detachSessionLocked() " + mSoundTriggerSession);
+            if (mSoundTriggerSession != null) {
+                mSoundTriggerSession.detach();
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index e31adcf..f2373fb 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -341,6 +341,9 @@
     @Nullable
     public DisplayShape displayShape;
 
+    /**
+     * Refresh rate range limitation based on the current device layout
+     */
     @Nullable
     public SurfaceControl.RefreshRateRange layoutLimitedRefreshRate;
 
@@ -354,7 +357,7 @@
      * RefreshRateRange limitation for @Temperature.ThrottlingStatus
      */
     @NonNull
-    public SparseArray<SurfaceControl.RefreshRateRange> refreshRateThermalThrottling =
+    public SparseArray<SurfaceControl.RefreshRateRange> thermalRefreshRateThrottling =
             new SparseArray<>();
 
     public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
@@ -434,7 +437,7 @@
                 && Objects.equals(displayShape, other.displayShape)
                 && Objects.equals(layoutLimitedRefreshRate, other.layoutLimitedRefreshRate)
                 && BrightnessSynchronizer.floatEquals(hdrSdrRatio, other.hdrSdrRatio)
-                && refreshRateThermalThrottling.contentEquals(other.refreshRateThermalThrottling);
+                && thermalRefreshRateThrottling.contentEquals(other.thermalRefreshRateThrottling);
     }
 
     @Override
@@ -491,7 +494,7 @@
         displayShape = other.displayShape;
         layoutLimitedRefreshRate = other.layoutLimitedRefreshRate;
         hdrSdrRatio = other.hdrSdrRatio;
-        refreshRateThermalThrottling = other.refreshRateThermalThrottling;
+        thermalRefreshRateThrottling = other.thermalRefreshRateThrottling;
     }
 
     public void readFromParcel(Parcel source) {
@@ -554,7 +557,7 @@
         displayShape = source.readTypedObject(DisplayShape.CREATOR);
         layoutLimitedRefreshRate = source.readTypedObject(SurfaceControl.RefreshRateRange.CREATOR);
         hdrSdrRatio = source.readFloat();
-        refreshRateThermalThrottling = source.readSparseArray(null,
+        thermalRefreshRateThrottling = source.readSparseArray(null,
                 SurfaceControl.RefreshRateRange.class);
     }
 
@@ -616,7 +619,7 @@
         dest.writeTypedObject(displayShape, flags);
         dest.writeTypedObject(layoutLimitedRefreshRate, flags);
         dest.writeFloat(hdrSdrRatio);
-        dest.writeSparseArray(refreshRateThermalThrottling);
+        dest.writeSparseArray(thermalRefreshRateThrottling);
     }
 
     @Override
@@ -884,8 +887,8 @@
         } else {
             sb.append(hdrSdrRatio);
         }
-        sb.append(", refreshRateThermalThrottling ");
-        sb.append(refreshRateThermalThrottling);
+        sb.append(", thermalRefreshRateThrottling ");
+        sb.append(thermalRefreshRateThrottling);
         sb.append("}");
         return sb.toString();
     }
diff --git a/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl b/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl
index 1ccc71a..23de50c 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl
@@ -94,4 +94,9 @@
      */
     @nullable SoundTrigger.ModelParamRange queryParameter(int keyphraseId,
             in ModelParams modelParam);
+    /**
+     * Invalidates the sound trigger session and clears any associated resources. Subsequent calls
+     * to this object will throw IllegalStateException.
+     */
+    void detach();
 }
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index a57a051..fd74185 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -133,6 +133,10 @@
     <string name="config_pointing_ui_package" translatable="false"></string>
     <java-symbol type="string" name="config_pointing_ui_package" />
 
+    <!-- Telephony pointing UI class name to be launched. -->
+    <string name="config_pointing_ui_class" translatable="false"></string>
+    <java-symbol type="string" name="config_pointing_ui_class" />
+
     <!-- Telephony resends received satellite datagram to listener
          if ack is not received within this timeout -->
     <integer name="config_timeout_to_receive_delivered_ack_millis">300000</integer>
diff --git a/core/tests/coretests/src/android/util/RotationUtilsTest.java b/core/tests/coretests/src/android/util/RotationUtilsTest.java
index 1b1ee4f..06b28f5 100644
--- a/core/tests/coretests/src/android/util/RotationUtilsTest.java
+++ b/core/tests/coretests/src/android/util/RotationUtilsTest.java
@@ -19,6 +19,7 @@
 import static android.util.RotationUtils.rotateBounds;
 import static android.util.RotationUtils.rotatePoint;
 import static android.util.RotationUtils.rotatePointF;
+import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
@@ -103,4 +104,19 @@
         assertEquals(560f, testResult.x, .1f);
         assertEquals(60f, testResult.y, .1f);
     }
+
+    @Test
+    public void testReverseRotationDirectionAroundZAxis() {
+        assertEquals(ROTATION_90,
+                RotationUtils.reverseRotationDirectionAroundZAxis(ROTATION_270));
+        assertEquals(ROTATION_270,
+                RotationUtils.reverseRotationDirectionAroundZAxis(ROTATION_90));
+        assertEquals(ROTATION_0,
+                RotationUtils.reverseRotationDirectionAroundZAxis(ROTATION_0));
+        assertEquals(ROTATION_180,
+                RotationUtils.reverseRotationDirectionAroundZAxis(ROTATION_180));
+
+        assertEquals(-1,
+                RotationUtils.reverseRotationDirectionAroundZAxis(-1));
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 27eda16..9467578 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -193,8 +193,11 @@
             // This Transition animates a task to fullscreen after being dragged from the status
             // bar and then released back into the status bar area
             final SurfaceControl sc = change.getLeash();
-            startT.setWindowCrop(sc, null);
-            startT.apply();
+            // Hide the first (fullscreen) frame because the animation will start from the smaller
+            // scale size.
+            startT.hide(sc)
+                    .setWindowCrop(sc, endBounds.width(), endBounds.height())
+                    .apply();
 
             final ValueAnimator animator = new ValueAnimator();
             animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f);
@@ -202,10 +205,10 @@
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
             animator.addUpdateListener(animation -> {
                 final float scale = animation.getAnimatedFraction();
-                t.setPosition(sc, mStartPosition.x * (1 - scale),
-                        mStartPosition.y * (1 - scale));
-                t.setScale(sc, scale, scale);
-                t.apply();
+                t.setPosition(sc, mStartPosition.x * (1 - scale), mStartPosition.y * (1 - scale))
+                        .setScale(sc, scale, scale)
+                        .show(sc)
+                        .apply();
             });
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index 248a5fc..fa3eee2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -129,8 +129,12 @@
             final int screenWidth = metrics.widthPixels;
             final int screenHeight = metrics.heightPixels;
             final SurfaceControl sc = change.getLeash();
-            startT.setCrop(sc, null);
-            startT.apply();
+            final Rect endBounds = change.getEndAbsBounds();
+            // Hide the first (fullscreen) frame because the animation will start from the freeform
+            // size.
+            startT.hide(sc)
+                    .setWindowCrop(sc, endBounds.width(), endBounds.height())
+                    .apply();
             final ValueAnimator animator = new ValueAnimator();
             animator.setFloatValues(0f, 1f);
             animator.setDuration(FULLSCREEN_ANIMATION_DURATION);
@@ -144,9 +148,10 @@
                 float fraction = animation.getAnimatedFraction();
                 float currentScaleX = scaleX + ((1 - scaleX) * fraction);
                 float currentScaleY = scaleY + ((1 - scaleY) * fraction);
-                t.setPosition(sc, startPos.x * (1 - fraction), startPos.y * (1 - fraction));
-                t.setScale(sc, currentScaleX, currentScaleY);
-                t.apply();
+                t.setPosition(sc, startPos.x * (1 - fraction), startPos.y * (1 - fraction))
+                        .setScale(sc, currentScaleX, currentScaleY)
+                        .show(sc)
+                        .apply();
             });
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index 2c5a5cd..4fad054 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -71,12 +71,6 @@
     @Mock
     Resources mResources;
     @Mock
-    SurfaceControl.Transaction mStartT;
-    @Mock
-    SurfaceControl.Transaction mFinishT;
-    @Mock
-    SurfaceControl.Transaction mAnimationT;
-    @Mock
     Transitions.TransitionFinishCallback mTransitionFinishCallback;
     @Mock
     ShellExecutor mExecutor;
@@ -88,7 +82,7 @@
         MockitoAnnotations.initMocks(this);
 
         doReturn(mExecutor).when(mTransitions).getMainExecutor();
-        doReturn(mAnimationT).when(mTransactionFactory).get();
+        doReturn(new SurfaceControl.Transaction()).when(mTransactionFactory).get();
         doReturn(mResources).when(mContext).getResources();
         doReturn(mDisplayMetrics).when(mResources).getDisplayMetrics();
         when(mResources.getDisplayMetrics())
@@ -115,7 +109,9 @@
         runOnUiThread(() -> {
             try {
                 assertTrue(mExitDesktopTaskTransitionHandler
-                        .startAnimation(mToken, info, mStartT, mFinishT,
+                        .startAnimation(mToken, info,
+                                new SurfaceControl.Transaction(),
+                                new SurfaceControl.Transaction(),
                                 mTransitionFinishCallback));
             } catch (Exception e) {
                 exceptions.add(e);
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 80a3e70..d749b91 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -223,7 +223,8 @@
                 break;
             }
             case DO_TIME_SHIFT_SET_MODE: {
-                mTvInputSessionImpl.timeShiftSetMode((Integer) msg.obj);
+                mTvInputSessionImpl.timeShiftSetMode(msg.arg1);
+                break;
             }
             case DO_TIME_SHIFT_ENABLE_POSITION_TRACKING: {
                 mTvInputSessionImpl.timeShiftEnablePositionTracking((Boolean) msg.obj);
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
deleted file mode 100644
index 09837df62..0000000
--- a/packages/CredentialManager/res/values/colors.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-  <color name="purple_200">#FFBB86FC</color>
-  <color name="purple_500">#FF6200EE</color>
-  <color name="purple_700">#FF3700B3</color>
-  <color name="teal_200">#FF03DAC5</color>
-  <color name="teal_700">#FF018786</color>
-  <color name="black">#FF000000</color>
-  <color name="white">#FFFFFFFF</color>
-</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 3e65251..905e0ca 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -1,4 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
   <!-- The name of this application. Credential Manager is a service that centralizes and provides
   access to a user's credentials used to sign in to various apps. [CHAR LIMIT=80] -->
diff --git a/packages/CredentialManager/res/values/themes.xml b/packages/CredentialManager/res/values/themes.xml
index 428c85a..22329e9f 100644
--- a/packages/CredentialManager/res/values/themes.xml
+++ b/packages/CredentialManager/res/values/themes.xml
@@ -1,4 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
 <resources>
   <style name="Theme.CredentialSelector" parent="@*android:style/ThemeOverlay.DeviceDefault.Accent.DayNight">
     <item name="android:windowContentOverlay">@null</item>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 8b9c8b9..6549b2d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -16,6 +16,7 @@
 
 package com.android.credentialmanager
 
+import android.app.Activity
 import android.content.Intent
 import android.credentials.ui.BaseDialogResult
 import android.credentials.ui.RequestInfo
@@ -47,6 +48,8 @@
 class CredentialSelectorActivity : ComponentActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+        overrideActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN,
+            0, 0)
         Log.d(Constants.LOG_TAG, "Creating new CredentialSelectorActivity")
         try {
             val (isCancellationRequest, shouldShowCancellationUi, _) =
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
index 10a75d4..250e3b1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
@@ -454,7 +454,7 @@
     if (color.isSpecified) {
         val alpha by animateFloatAsState(
             targetValue = if (visible) 1f else 0f,
-            animationSpec = TweenSpec()
+            animationSpec = TweenSpec(durationMillis = SwipeableDefaults.DefaultDurationMillis)
         )
         LocalConfiguration.current
         val resources = LocalContext.current.resources
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/Swipeable.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/Swipeable.kt
index 3e2de83..e9aaeee 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/material/Swipeable.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/Swipeable.kt
@@ -19,6 +19,7 @@
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.SpringSpec
+import androidx.compose.animation.core.Spring
 import androidx.compose.foundation.gestures.DraggableState
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.draggable
@@ -791,7 +792,12 @@
     /**
      * The default animation used by [SwipeableState].
      */
-    val AnimationSpec = SpringSpec<Float>()
+    val AnimationSpec = SpringSpec<Float>(stiffness = Spring.StiffnessMediumLow)
+
+    /**
+     * The default animation duration used by Scrim in enter/exit transitions.
+     */
+    val DefaultDurationMillis: Int = 400
 
     /**
      * The default velocity threshold (1.8 dp per millisecond) used by [swipeable].
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index 53731f0..3399681 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -20,6 +20,11 @@
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import com.android.compose.rememberSystemUiController
@@ -28,6 +33,8 @@
 import com.android.credentialmanager.common.material.rememberModalBottomSheetState
 import com.android.credentialmanager.ui.theme.EntryShape
 import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
+import kotlinx.coroutines.launch
+
 
 /** Draws a modal bottom sheet with the same styles and effects shared by various flows. */
 @Composable
@@ -35,8 +42,10 @@
     sheetContent: @Composable ColumnScope.() -> Unit,
     onDismiss: () -> Unit
 ) {
+    var isInitialRender by remember { mutableStateOf(true) }
+    val scope = rememberCoroutineScope()
     val state = rememberModalBottomSheetState(
-        initialValue = ModalBottomSheetValue.Expanded,
+        initialValue = ModalBottomSheetValue.Hidden,
         skipHalfExpanded = true
     )
     val sysUiController = rememberSystemUiController()
@@ -54,7 +63,12 @@
     ) {}
     LaunchedEffect(state.currentValue) {
         if (state.currentValue == ModalBottomSheetValue.Hidden) {
-            onDismiss()
+            if (isInitialRender) {
+                isInitialRender = false
+                scope.launch { state.show() }
+            } else {
+                onDismiss()
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index 4fd2b5d..e68ef85 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -14,17 +14,27 @@
  * limitations under the License.
  */
 
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
 buildscript {
     ext {
         BUILD_TOOLS_VERSION = "30.0.3"
         MIN_SDK = 21
         TARGET_SDK = 33
         jetpack_compose_version = '1.4.0-beta01'
-        jetpack_compose_compiler_version = '1.4.0'
+        jetpack_compose_compiler_version = '1.4.4'
     }
 }
 plugins {
-    id 'com.android.application' version '8.0.0-beta05' apply false
-    id 'com.android.library' version '8.0.0-beta05' apply false
-    id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
+    id 'com.android.application' version '8.0.0' apply false
+    id 'com.android.library' version '8.0.0' apply false
+    id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
+}
+subprojects {
+    tasks.withType(KotlinCompile).configureEach {
+        kotlinOptions {
+            jvmTarget = "17"
+            freeCompilerArgs = ["-Xjvm-default=all"]
+        }
+    }
 }
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
index 416a403..212aa7b 100644
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -42,12 +42,8 @@
         }
     }
     compileOptions {
-        sourceCompatibility JavaVersion.VERSION_11
-        targetCompatibility JavaVersion.VERSION_11
-    }
-    kotlinOptions {
-        jvmTarget = '11'
-        freeCompilerArgs = ["-Xjvm-default=all"]
+        sourceCompatibility JavaVersion.VERSION_17
+        targetCompatibility JavaVersion.VERSION_17
     }
     buildFeatures {
         compose true
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 9962c93..fb945a3 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -51,10 +51,6 @@
         sourceCompatibility JavaVersion.VERSION_17
         targetCompatibility JavaVersion.VERSION_17
     }
-    kotlinOptions {
-        jvmTarget = '17'
-        freeCompilerArgs = ["-Xjvm-default=all"]
-    }
     buildFeatures {
         compose true
     }
@@ -79,7 +75,7 @@
     api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
     api "androidx.lifecycle:lifecycle-livedata-ktx"
     api "androidx.lifecycle:lifecycle-runtime-compose"
-    api "androidx.navigation:navigation-compose:2.6.0-alpha07"
+    api "androidx.navigation:navigation-compose:2.6.0-alpha08"
     api "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
     api "com.google.android.material:material:1.7.0-alpha03"
     debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
index f0df9a6..8cbf7cc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
@@ -19,7 +19,6 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.selection.selectableGroup
 import androidx.compose.material.icons.Icons
@@ -40,7 +39,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -60,13 +58,17 @@
 
     Box(
         modifier = Modifier
-            .padding(SettingsDimension.itemPadding)
+            .padding(
+                start = SettingsDimension.itemPaddingStart,
+                top = SettingsDimension.itemPaddingAround,
+                end = SettingsDimension.itemPaddingEnd,
+                bottom = SettingsDimension.itemPaddingAround,
+            )
             .selectableGroup(),
     ) {
         val contentPadding = PaddingValues(horizontal = SettingsDimension.itemPaddingEnd)
         Button(
             onClick = { expanded = true },
-            modifier = Modifier.height(36.dp),
             colors = ButtonDefaults.buttonColors(
                 containerColor = SettingsTheme.colorScheme.spinnerHeaderContainer,
                 contentColor = SettingsTheme.colorScheme.onSpinnerHeaderContainer,
@@ -86,7 +88,6 @@
             expanded = expanded,
             onDismissRequest = { expanded = false },
             modifier = Modifier.background(SettingsTheme.colorScheme.spinnerItemContainer),
-            offset = DpOffset(x = 0.dp, y = 4.dp),
         ) {
             for (option in options) {
                 DropdownMenuItem(
@@ -116,7 +117,9 @@
 ) {
     Text(
         text = option?.text ?: "",
-        modifier = modifier.padding(end = SettingsDimension.itemPaddingEnd),
+        modifier = modifier
+            .padding(end = SettingsDimension.itemPaddingEnd)
+            .padding(vertical = SettingsDimension.itemPaddingAround),
         color = color,
         style = MaterialTheme.typography.labelLarge,
     )
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index e7f7db2..23a9add 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -41,10 +41,6 @@
         sourceCompatibility JavaVersion.VERSION_17
         targetCompatibility JavaVersion.VERSION_17
     }
-    kotlinOptions {
-        jvmTarget = '17'
-        freeCompilerArgs = ["-Xjvm-default=all"]
-    }
     buildFeatures {
         compose true
     }
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 8eb012d..5b5871f 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -43,7 +43,6 @@
         "androidx.core_core-ktx",
         "androidx.annotation_annotation",
         "SystemUIShaderLib",
-        "animationlib",
     ],
 
     manifest: "AndroidManifest.xml",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 2e80379..296c2ae 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -39,9 +39,9 @@
 import android.view.animation.PathInterpolator
 import androidx.annotation.BinderThread
 import androidx.annotation.UiThread
-import com.android.app.animation.Interpolators
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.policy.ScreenDecorationsUtils
+import java.lang.IllegalArgumentException
 import kotlin.math.roundToInt
 
 private const val TAG = "ActivityLaunchAnimator"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 48dd08f..42a8636 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -33,10 +33,10 @@
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
 import android.widget.FrameLayout
-import com.android.app.animation.Interpolators
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.jank.InteractionJankMonitor.CujType
 import com.android.systemui.util.registerAnimationOnBackInvoked
+import java.lang.IllegalArgumentException
 import kotlin.math.roundToInt
 
 private const val TAG = "DialogLaunchAnimator"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
new file mode 100644
index 0000000..9dbb920
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2016 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.systemui.animation;
+
+import android.graphics.Path;
+import android.util.MathUtils;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.BounceInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
+
+/**
+ * Utility class to receive interpolators from.
+ *
+ * Make sure that changes made to this class are also reflected in {@link InterpolatorsAndroidX}.
+ * Please consider using the androidx dependencies featuring better testability altogether.
+ */
+public class Interpolators {
+
+    /*
+     * ============================================================================================
+     * Emphasized interpolators.
+     * ============================================================================================
+     */
+
+    /**
+     * The default emphasized interpolator. Used for hero / emphasized movement of content.
+     */
+    public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
+
+    /**
+     * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
+     * is disappearing e.g. when moving off screen.
+     */
+    public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
+            0.3f, 0f, 0.8f, 0.15f);
+
+    /**
+     * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
+     * is appearing e.g. when coming from off screen
+     */
+    public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
+            0.05f, 0.7f, 0.1f, 1f);
+
+
+    /*
+     * ============================================================================================
+     * Standard interpolators.
+     * ============================================================================================
+     */
+
+    /**
+     * The standard interpolator that should be used on every normal animation
+     */
+    public static final Interpolator STANDARD = new PathInterpolator(
+            0.2f, 0f, 0f, 1f);
+
+    /**
+     * The standard accelerating interpolator that should be used on every regular movement of
+     * content that is disappearing e.g. when moving off screen.
+     */
+    public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(
+            0.3f, 0f, 1f, 1f);
+
+    /**
+     * The standard decelerating interpolator that should be used on every regular movement of
+     * content that is appearing e.g. when coming from off screen.
+     */
+    public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(
+            0f, 0f, 0f, 1f);
+
+    /*
+     * ============================================================================================
+     * Legacy
+     * ============================================================================================
+     */
+
+    /**
+     * The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN.
+     */
+    public static final Interpolator LEGACY = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
+    /**
+     * The default legacy accelerating interpolator as defined in Material 1.
+     * Also known as FAST_OUT_LINEAR_IN.
+     */
+    public static final Interpolator LEGACY_ACCELERATE = new PathInterpolator(0.4f, 0f, 1f, 1f);
+
+    /**
+     * The default legacy decelerating interpolator as defined in Material 1.
+     * Also known as LINEAR_OUT_SLOW_IN.
+     */
+    public static final Interpolator LEGACY_DECELERATE = new PathInterpolator(0f, 0f, 0.2f, 1f);
+
+    /**
+     * Linear interpolator. Often used if the interpolator is for different properties who need
+     * different interpolations.
+     */
+    public static final Interpolator LINEAR = new LinearInterpolator();
+
+    /*
+    * ============================================================================================
+    * Custom interpolators
+    * ============================================================================================
+    */
+
+    public static final Interpolator FAST_OUT_SLOW_IN = LEGACY;
+    public static final Interpolator FAST_OUT_LINEAR_IN = LEGACY_ACCELERATE;
+    public static final Interpolator LINEAR_OUT_SLOW_IN = LEGACY_DECELERATE;
+
+    /**
+     * Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t
+     * goes from 1 to 0 instead of 0 to 1).
+     */
+    public static final Interpolator FAST_OUT_SLOW_IN_REVERSE =
+            new PathInterpolator(0.8f, 0f, 0.6f, 1f);
+    public static final Interpolator SLOW_OUT_LINEAR_IN = new PathInterpolator(0.8f, 0f, 1f, 1f);
+    public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+    public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
+    public static final Interpolator ACCELERATE = new AccelerateInterpolator();
+    public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
+    public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
+    public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
+    public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
+    public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f,
+            1.1f);
+    public static final Interpolator PANEL_CLOSE_ACCELERATED = new PathInterpolator(0.3f, 0, 0.5f,
+            1);
+    public static final Interpolator BOUNCE = new BounceInterpolator();
+    /**
+     * For state transitions on the control panel that lives in GlobalActions.
+     */
+    public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f,
+            1.0f);
+
+    /**
+     * Interpolator to be used when animating a move based on a click. Pair with enough duration.
+     */
+    public static final Interpolator TOUCH_RESPONSE =
+            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+
+    /**
+     * Like {@link #TOUCH_RESPONSE}, but used in case the animation is played in reverse (i.e. t
+     * goes from 1 to 0 instead of 0 to 1).
+     */
+    public static final Interpolator TOUCH_RESPONSE_REVERSE =
+            new PathInterpolator(0.9f, 0f, 0.7f, 1f);
+
+    /*
+     * ============================================================================================
+     * Functions / Utilities
+     * ============================================================================================
+     */
+
+    /**
+     * Calculate the amount of overshoot using an exponential falloff function with desired
+     * properties, where the overshoot smoothly transitions at the 1.0f boundary into the
+     * overshoot, retaining its acceleration.
+     *
+     * @param progress a progress value going from 0 to 1
+     * @param overshootAmount the amount > 0 of overshoot desired. A value of 0.1 means the max
+     *                        value of the overall progress will be at 1.1.
+     * @param overshootStart the point in (0,1] where the result should reach 1
+     * @return the interpolated overshoot
+     */
+    public static float getOvershootInterpolation(float progress, float overshootAmount,
+            float overshootStart) {
+        if (overshootAmount == 0.0f || overshootStart == 0.0f) {
+            throw new IllegalArgumentException("Invalid values for overshoot");
+        }
+        float b = MathUtils.log((overshootAmount + 1) / (overshootAmount)) / overshootStart;
+        return MathUtils.max(0.0f,
+                (float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f));
+    }
+
+    /**
+     * Similar to {@link #getOvershootInterpolation(float, float, float)} but the overshoot
+     * starts immediately here, instead of first having a section of non-overshooting
+     *
+     * @param progress a progress value going from 0 to 1
+     */
+    public static float getOvershootInterpolation(float progress) {
+        return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress)));
+    }
+
+    // Create the default emphasized interpolator
+    private static PathInterpolator createEmphasizedInterpolator() {
+        Path path = new Path();
+        // Doing the same as fast_out_extra_slow_in
+        path.moveTo(0f, 0f);
+        path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
+        path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
+        return new PathInterpolator(path);
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java b/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
new file mode 100644
index 0000000..8da87feb
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
@@ -0,0 +1,219 @@
+/*
+ * 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.systemui.animation;
+
+import android.graphics.Path;
+import android.util.MathUtils;
+
+import androidx.core.animation.AccelerateDecelerateInterpolator;
+import androidx.core.animation.AccelerateInterpolator;
+import androidx.core.animation.BounceInterpolator;
+import androidx.core.animation.DecelerateInterpolator;
+import androidx.core.animation.Interpolator;
+import androidx.core.animation.LinearInterpolator;
+import androidx.core.animation.PathInterpolator;
+
+/**
+ * Utility class to receive interpolators from. (androidx compatible version)
+ *
+ * This is the androidx compatible version of {@link Interpolators}. Make sure that changes made to
+ * this class are also reflected in {@link Interpolators}.
+ *
+ * Using the androidx versions of {@link androidx.core.animation.ValueAnimator} or
+ * {@link androidx.core.animation.ObjectAnimator} improves animation testability. This file provides
+ * the androidx compatible versions of the interpolators defined in {@link Interpolators}.
+ * AnimatorTestRule can be used in Tests to manipulate the animation under test (e.g. artificially
+ * advancing the time).
+ */
+public class InterpolatorsAndroidX {
+
+    /*
+     * ============================================================================================
+     * Emphasized interpolators.
+     * ============================================================================================
+     */
+
+    /**
+     * The default emphasized interpolator. Used for hero / emphasized movement of content.
+     */
+    public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
+
+    /**
+     * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
+     * is disappearing e.g. when moving off screen.
+     */
+    public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
+            0.3f, 0f, 0.8f, 0.15f);
+
+    /**
+     * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
+     * is appearing e.g. when coming from off screen
+     */
+    public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
+            0.05f, 0.7f, 0.1f, 1f);
+
+
+    /*
+     * ============================================================================================
+     * Standard interpolators.
+     * ============================================================================================
+     */
+
+    /**
+     * The standard interpolator that should be used on every normal animation
+     */
+    public static final Interpolator STANDARD = new PathInterpolator(
+            0.2f, 0f, 0f, 1f);
+
+    /**
+     * The standard accelerating interpolator that should be used on every regular movement of
+     * content that is disappearing e.g. when moving off screen.
+     */
+    public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(
+            0.3f, 0f, 1f, 1f);
+
+    /**
+     * The standard decelerating interpolator that should be used on every regular movement of
+     * content that is appearing e.g. when coming from off screen.
+     */
+    public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(
+            0f, 0f, 0f, 1f);
+
+    /*
+     * ============================================================================================
+     * Legacy
+     * ============================================================================================
+     */
+
+    /**
+     * The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN.
+     */
+    public static final Interpolator LEGACY = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
+    /**
+     * The default legacy accelerating interpolator as defined in Material 1.
+     * Also known as FAST_OUT_LINEAR_IN.
+     */
+    public static final Interpolator LEGACY_ACCELERATE = new PathInterpolator(0.4f, 0f, 1f, 1f);
+
+    /**
+     * The default legacy decelerating interpolator as defined in Material 1.
+     * Also known as LINEAR_OUT_SLOW_IN.
+     */
+    public static final Interpolator LEGACY_DECELERATE = new PathInterpolator(0f, 0f, 0.2f, 1f);
+
+    /**
+     * Linear interpolator. Often used if the interpolator is for different properties who need
+     * different interpolations.
+     */
+    public static final Interpolator LINEAR = new LinearInterpolator();
+
+    /*
+    * ============================================================================================
+    * Custom interpolators
+    * ============================================================================================
+    */
+
+    public static final Interpolator FAST_OUT_SLOW_IN = LEGACY;
+    public static final Interpolator FAST_OUT_LINEAR_IN = LEGACY_ACCELERATE;
+    public static final Interpolator LINEAR_OUT_SLOW_IN = LEGACY_DECELERATE;
+
+    /**
+     * Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t
+     * goes from 1 to 0 instead of 0 to 1).
+     */
+    public static final Interpolator FAST_OUT_SLOW_IN_REVERSE =
+            new PathInterpolator(0.8f, 0f, 0.6f, 1f);
+    public static final Interpolator SLOW_OUT_LINEAR_IN = new PathInterpolator(0.8f, 0f, 1f, 1f);
+    public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+    public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
+    public static final Interpolator ACCELERATE = new AccelerateInterpolator();
+    public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
+    public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
+    public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
+    public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
+    public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f,
+            1.1f);
+    public static final Interpolator PANEL_CLOSE_ACCELERATED = new PathInterpolator(0.3f, 0, 0.5f,
+            1);
+    public static final Interpolator BOUNCE = new BounceInterpolator();
+    /**
+     * For state transitions on the control panel that lives in GlobalActions.
+     */
+    public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f,
+            1.0f);
+
+    /**
+     * Interpolator to be used when animating a move based on a click. Pair with enough duration.
+     */
+    public static final Interpolator TOUCH_RESPONSE =
+            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+
+    /**
+     * Like {@link #TOUCH_RESPONSE}, but used in case the animation is played in reverse (i.e. t
+     * goes from 1 to 0 instead of 0 to 1).
+     */
+    public static final Interpolator TOUCH_RESPONSE_REVERSE =
+            new PathInterpolator(0.9f, 0f, 0.7f, 1f);
+
+    /*
+     * ============================================================================================
+     * Functions / Utilities
+     * ============================================================================================
+     */
+
+    /**
+     * Calculate the amount of overshoot using an exponential falloff function with desired
+     * properties, where the overshoot smoothly transitions at the 1.0f boundary into the
+     * overshoot, retaining its acceleration.
+     *
+     * @param progress a progress value going from 0 to 1
+     * @param overshootAmount the amount > 0 of overshoot desired. A value of 0.1 means the max
+     *                        value of the overall progress will be at 1.1.
+     * @param overshootStart the point in (0,1] where the result should reach 1
+     * @return the interpolated overshoot
+     */
+    public static float getOvershootInterpolation(float progress, float overshootAmount,
+            float overshootStart) {
+        if (overshootAmount == 0.0f || overshootStart == 0.0f) {
+            throw new IllegalArgumentException("Invalid values for overshoot");
+        }
+        float b = MathUtils.log((overshootAmount + 1) / (overshootAmount)) / overshootStart;
+        return MathUtils.max(0.0f,
+                (float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f));
+    }
+
+    /**
+     * Similar to {@link #getOvershootInterpolation(float, float, float)} but the overshoot
+     * starts immediately here, instead of first having a section of non-overshooting
+     *
+     * @param progress a progress value going from 0 to 1
+     */
+    public static float getOvershootInterpolation(float progress) {
+        return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress)));
+    }
+
+    // Create the default emphasized interpolator
+    private static PathInterpolator createEmphasizedInterpolator() {
+        Path path = new Path();
+        // Doing the same as fast_out_extra_slow_in
+        path.moveTo(0f, 0f);
+        path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
+        path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
+        return new PathInterpolator(path);
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
index 142fd21..3417ffd 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
@@ -28,7 +28,7 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.animation.Interpolator
-import com.android.app.animation.Interpolators.LINEAR
+import com.android.systemui.animation.Interpolators.LINEAR
 import kotlin.math.roundToInt
 
 private const val TAG = "LaunchAnimator"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 8e79e3c..58ffef2 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -25,7 +25,6 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.animation.Interpolator
-import com.android.app.animation.Interpolators
 import kotlin.math.max
 import kotlin.math.min
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
index dd32851..f3d8b17 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
@@ -19,7 +19,7 @@
 import android.util.DisplayMetrics
 import android.view.animation.Interpolator
 import android.window.BackEvent
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.util.dpToPx
 
 /** Used to convert [BackEvent] into a [BackTransformation]. */
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 3688f9e..9d1dd1b 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -29,9 +29,9 @@
 import android.util.AttributeSet
 import android.util.MathUtils
 import android.widget.TextView
-import com.android.app.animation.Interpolators
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.animation.GlyphCallback
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.TextAnimator
 import com.android.systemui.customization.R
 import com.android.systemui.plugins.log.LogBuffer
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 6d4dbf6..33c7c11 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -102,6 +102,33 @@
     void dismissKeyguardThenExecute(OnDismissAction action, @Nullable Runnable cancel,
             boolean afterKeyguardGone, @Nullable String customMessage);
 
+    /** Starts an activity and dismisses keyguard. */
+    void startActivityDismissingKeyguard(Intent intent,
+            boolean onlyProvisioned,
+            boolean dismissShade,
+            boolean disallowEnterPictureInPictureWhileLaunching,
+            Callback callback,
+            int flags,
+            @Nullable ActivityLaunchAnimator.Controller animationController,
+            UserHandle userHandle);
+
+    /** Execute a runnable after dismissing keyguard. */
+    void executeRunnableDismissingKeyguard(Runnable runnable,
+            Runnable cancelAction,
+            boolean dismissShade,
+            boolean afterKeyguardGone,
+            boolean deferred);
+
+    /** Execute a runnable after dismissing keyguard. */
+    void executeRunnableDismissingKeyguard(
+            Runnable runnable,
+            Runnable cancelAction,
+            boolean dismissShade,
+            boolean afterKeyguardGone,
+            boolean deferred,
+            boolean willAnimateOnKeyguard,
+            @Nullable String customMessage);
+
     interface Callback {
         void onActivityStarted(int resultCode);
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 64d766d..f59bf8e 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -26,8 +26,8 @@
 import android.graphics.Color
 import android.util.AttributeSet
 import android.view.View
-import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
+import com.android.systemui.animation.Interpolators
 
 /** Displays security messages for the keyguard bouncer. */
 open class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index a30cae9..a6c782d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -15,9 +15,9 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.app.animation.Interpolators;
 import com.android.keyguard.dagger.KeyguardStatusViewScope;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.plugins.log.LogLevel;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 0982030..0394754 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -32,9 +32,9 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.ConstraintSet;
 
-import com.android.app.animation.Interpolators;
 import com.android.settingslib.animation.DisappearAnimationUtils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
 
 /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 1d7c35d..33bea02 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -45,11 +45,11 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.TextViewInputDisabler;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 /**
  * Displays an alphanumeric (latin-1) key entry for the user to enter
  * an unlock password
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index b4ddc9a..0a91150 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -34,9 +34,9 @@
 import android.view.KeyEvent;
 import android.view.View;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 78021ad..ba5a8c9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -32,7 +32,7 @@
 import static androidx.constraintlayout.widget.ConstraintSet.TOP;
 import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
 
-import static com.android.app.animation.InterpolatorsAndroidX.DECELERATE_QUINT;
+import static com.android.systemui.animation.InterpolatorsAndroidX.DECELERATE_QUINT;
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
 
 import static java.lang.Integer.max;
@@ -86,7 +86,6 @@
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringAnimation;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
@@ -97,6 +96,7 @@
 import com.android.settingslib.drawable.CircleFramedDrawable;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.shared.system.SysUiStatsLog;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
index 96ac8ad..c9128e5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
@@ -26,9 +26,9 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.animation.AnimationUtils
-import com.android.app.animation.Interpolators
 import com.android.internal.R.interpolator.fast_out_extra_slow_in
 import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
 
 /** Animates constraint layout changes for the security view. */
 class KeyguardSecurityViewTransition : Transition() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index b4f124a..65a7166 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -44,11 +44,11 @@
 import androidx.slice.widget.RowContent;
 import androidx.slice.widget.SliceContent;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
 
 import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 89e7e17..edfcb8d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -40,11 +40,11 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.ConstraintSet;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ClockController;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index aa652fa..651c979 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -21,7 +21,7 @@
 import android.util.Property;
 import android.view.View;
 
-import com.android.app.animation.Interpolators;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.plugins.log.LogLevel;
 import com.android.systemui.statusbar.StatusBarState;
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index e761123..ad66909 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -30,7 +30,7 @@
 
 import androidx.annotation.StyleRes;
 
-import com.android.app.animation.Interpolators;
+import com.android.systemui.animation.Interpolators;
 
 /**
  * Provides background color and radius animations for key pad buttons.
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
index c4ecb39..14810d9 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
@@ -35,9 +35,9 @@
 
 import androidx.core.graphics.drawable.DrawableCompat;
 
-import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 
 /**
  * This class contains implementation for methods that will be used when user has set a
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
index 401f6c9..bf84f8a 100644
--- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
@@ -35,6 +35,8 @@
 /**
  * Single common instance of ActivityStarter that can be gotten and referenced from anywhere, but
  * delegates to an actual implementation (CentralSurfaces).
+ *
+ * @deprecated Migrating to ActivityStarterImpl
  */
 @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
 @SysUISingleton
@@ -92,6 +94,14 @@
     }
 
     @Override
+    public void startActivity(Intent intent,
+            boolean dismissShade,
+            @Nullable ActivityLaunchAnimator.Controller animationController) {
+        mActualStarterOptionalLazy.get().ifPresent(
+                starter -> starter.startActivity(intent, dismissShade, animationController));
+    }
+
+    @Override
     public void startActivity(Intent intent, boolean dismissShade,
             @Nullable ActivityLaunchAnimator.Controller animationController,
             boolean showOverLockscreenWhenLocked) {
@@ -177,4 +187,35 @@
                 starter -> starter.dismissKeyguardThenExecute(action, cancel, afterKeyguardGone,
                         customMessage));
     }
+
+    @Override
+    public void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned,
+            boolean dismissShade, boolean disallowEnterPictureInPictureWhileLaunching,
+            Callback callback, int flags,
+            @Nullable ActivityLaunchAnimator.Controller animationController,
+            UserHandle userHandle) {
+        mActualStarterOptionalLazy.get().ifPresent(
+                starter -> starter.startActivityDismissingKeyguard(intent, onlyProvisioned,
+                        dismissShade, disallowEnterPictureInPictureWhileLaunching, callback,
+                        flags, animationController, userHandle));
+    }
+
+    @Override
+    public void executeRunnableDismissingKeyguard(Runnable runnable,
+            Runnable cancelAction, boolean dismissShade,
+            boolean afterKeyguardGone, boolean deferred) {
+        mActualStarterOptionalLazy.get().ifPresent(
+                starter -> starter.executeRunnableDismissingKeyguard(runnable, cancelAction,
+                        dismissShade, afterKeyguardGone, deferred));
+    }
+
+    @Override
+    public void executeRunnableDismissingKeyguard(Runnable runnable, Runnable cancelAction,
+            boolean dismissShade, boolean afterKeyguardGone, boolean deferred,
+            boolean willAnimateOnKeyguard, @Nullable String customMessage) {
+        mActualStarterOptionalLazy.get().ifPresent(
+                starter -> starter.executeRunnableDismissingKeyguard(runnable, cancelAction,
+                        dismissShade, afterKeyguardGone, deferred, willAnimateOnKeyguard,
+                        customMessage));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index c1871e0..de82ca0 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -36,7 +36,7 @@
 import android.view.View
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.util.asIndenting
 import java.io.PrintWriter
 
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index e72ad82..a3e7d71 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -34,7 +34,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.settingslib.Utils
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.log.ScreenDecorationsLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 9adfcc9..aa94ad9 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -43,8 +43,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.dynamicanimation.animation.SpringForce;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
@@ -374,11 +374,10 @@
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
                 final boolean captured = (mIsSwiping || mLongPressSent || mMenuRowIntercepting);
-                mIsSwiping = false;
-                mTouchedView = null;
                 mLongPressSent = false;
                 mCallback.onLongPressSent(null);
                 mMenuRowIntercepting = false;
+                resetSwipeState();
                 cancelLongPress();
                 if (captured) return true;
                 break;
@@ -491,7 +490,7 @@
                 }
                 if (!mCancelled || wasRemoved) {
                     mCallback.onChildDismissed(animView);
-                    resetSwipeState();
+                    resetSwipeOfView(animView);
                 }
                 if (endAction != null) {
                     endAction.accept(mCancelled);
@@ -546,7 +545,7 @@
 
             if (!cancelled) {
                 updateSwipeProgressFromOffset(animView, canBeDismissed);
-                resetSwipeState();
+                resetSwipeOfView(animView);
             }
             onChildSnappedBack(animView, targetLeft);
         });
@@ -806,9 +805,20 @@
         return mIsSwiping ? mTouchedView : null;
     }
 
+    protected void resetSwipeOfView(View view) {
+        if (getSwipedView() == view) {
+            resetSwipeState();
+        }
+    }
+
     public void resetSwipeState() {
+        View swipedView = getSwipedView();
         mTouchedView = null;
         mIsSwiping = false;
+        if (swipedView != null) {
+            snapChildIfNeeded(swipedView, false, 0);
+            onChildSnappedBack(swipedView, 0);
+        }
     }
 
     private float getTouchSlop(MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
index d491975..d6f0b59 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
@@ -32,8 +32,8 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 
-import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 
 /**
  * Visually discloses that contextual data was provided to an assistant.
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 2aac056..0002ae9 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -45,9 +45,9 @@
 import androidx.annotation.StyleRes;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.app.animation.Interpolators;
 import com.android.systemui.DualToneHandler;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.policy.BatteryController;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index be585ed..aeebb01 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -59,11 +59,11 @@
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
 import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
 import com.android.systemui.biometrics.ui.CredentialView;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 782a10b..d0ac296 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -32,7 +32,7 @@
 import com.android.settingslib.udfps.UdfpsOverlayParams
 import com.android.systemui.CoreStartable
 import com.android.systemui.R
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 5ede16d..b007134 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -28,7 +28,7 @@
 import android.view.View
 import android.view.animation.PathInterpolator
 import com.android.internal.graphics.ColorUtils
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.surfaceeffects.ripple.RippleShader
 
 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 1dbafc6..ef7dcb7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -19,7 +19,7 @@
 import android.graphics.PointF
 import android.graphics.RectF
 import com.android.systemui.Dumpable
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionListener
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index 52db4ab..ba8e60a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -40,9 +40,9 @@
 import androidx.annotation.Nullable;
 import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
 
-import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 
 import com.airbnb.lottie.LottieAnimationView;
 import com.airbnb.lottie.LottieProperty;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index eaab75a..3b50bbc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -23,11 +23,11 @@
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.animation.Interpolators
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index 9292bd7..e2d36dc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -6,8 +6,8 @@
 import android.widget.TextView
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.animation.Interpolators
 import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.biometrics.AuthDialog
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.ui.CredentialPasswordView
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 7bf8f4d..11ef749 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -30,9 +30,9 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
-import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.surfaceeffects.ripple.RippleShader;
 import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
index b447d66..8d0edf8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
@@ -32,7 +32,7 @@
 import androidx.lifecycle.LifecycleObserver
 import androidx.lifecycle.OnLifecycleEvent
 import com.android.systemui.R
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.controls.ui.ControlsUiController
 
 object ControlsAnimations {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index e6361f4..6a9aaf8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -50,7 +50,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.internal.graphics.ColorUtils
 import com.android.systemui.R
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.util.concurrency.DelayableExecutor
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index 1461135..fa36eee 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -38,7 +38,7 @@
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
 import com.android.systemui.R
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL
 import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL
 import java.util.IllegalFormatException
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index f6435a7..0dcba50 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -278,6 +278,7 @@
 
     @Provides
     @Singleton
+    @Nullable
     static IVrManager provideIVrManager() {
         return IVrManager.Stub.asInterface(ServiceManager.getService(Context.VR_SERVICE));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
index 67ad3db..8764297 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
@@ -18,6 +18,8 @@
 
 import com.android.systemui.ActivityStarterDelegate;
 import com.android.systemui.classifier.FalsingManagerProxy;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.globalactions.GlobalActionsComponent;
 import com.android.systemui.globalactions.GlobalActionsImpl;
 import com.android.systemui.plugins.ActivityStarter;
@@ -28,6 +30,7 @@
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
+import com.android.systemui.statusbar.phone.ActivityStarterImpl;
 import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
 import com.android.systemui.volume.VolumeDialogControllerImpl;
 
@@ -46,7 +49,11 @@
     /** */
     @Provides
     static ActivityStarter provideActivityStarter(ActivityStarterDelegate delegate,
-            PluginDependencyProvider dependencyProvider) {
+            PluginDependencyProvider dependencyProvider, ActivityStarterImpl activityStarterImpl,
+            FeatureFlags featureFlags) {
+        if (featureFlags.isEnabled(Flags.USE_NEW_ACTIVITY_STARTER)) {
+            return activityStarterImpl;
+        }
         dependencyProvider.allowPluginDependency(ActivityStarter.class, delegate);
         return delegate;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 83f39b5..5b56c04 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -24,8 +24,8 @@
 import androidx.core.animation.doOnEnd
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.animation.Interpolators
 import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.complication.ComplicationHostViewController
 import com.android.systemui.complication.ComplicationLayoutParams
 import com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTOM
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index c22019e..15a32d2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -33,9 +33,9 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.app.animation.Interpolators;
 import com.android.dream.lowlight.LowLightTransitionCoordinator;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.complication.ComplicationHostViewController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 0f370ac..b141db1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -199,7 +199,8 @@
                     if (mShouldShowComplications) {
                         return (requiredTypes & getAvailableComplicationTypes()) == requiredTypes;
                     }
-                    return (requiredTypes & mSupportedTypes) == requiredTypes;
+                    final int typesToAlwaysShow = mSupportedTypes & getAvailableComplicationTypes();
+                    return (requiredTypes & typesToAlwaysShow) == requiredTypes;
                 })
                 .collect(Collectors.toCollection(HashSet::new))
                 : mComplications);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 2ecb0a0..17e66a7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -675,4 +675,8 @@
     @JvmField
     val ADVANCED_VPN_ENABLED = unreleasedFlag(2800, name = "AdvancedVpn__enable_feature",
             namespace = "vpn", teamfood = true)
+
+    // TODO(b/278761837): Tracking Bug
+    @JvmField
+    val USE_NEW_ACTIVITY_STARTER = unreleasedFlag(2801, name = "use_new_activity_starter")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 5189944..d3b6fc2 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -97,7 +97,6 @@
 import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.LifecycleRegistry;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
@@ -117,6 +116,7 @@
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.animation.Expandable;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Background;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index d3fe2c5..8c0cfba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -23,6 +23,7 @@
 import android.text.TextUtils;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.Dumpable;
@@ -74,7 +75,9 @@
 
     // Executor that will show the next message after a delay
     private final DelayableExecutor mExecutor;
-    @Nullable private ShowNextIndication mShowNextIndicationRunnable;
+
+    @VisibleForTesting
+    @Nullable ShowNextIndication mShowNextIndicationRunnable;
 
     // List of indication types to show. The next indication to show is always at index 0
     private final List<Integer> mIndicationQueue = new ArrayList<>();
@@ -111,6 +114,12 @@
         cancelScheduledIndication();
     }
 
+    /** Destroy ViewController, removing any listeners. */
+    public void destroy() {
+        super.destroy();
+        onViewDetached();
+    }
+
     /**
      * Update the indication type with the given String.
      * @param type of indication
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 9844ca0..2925d8d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -36,7 +36,7 @@
 import com.android.internal.R
 import com.android.keyguard.KeyguardClockSwitchController
 import com.android.keyguard.KeyguardViewController
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 99a9bed5..93ddfba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -98,7 +98,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -122,6 +121,7 @@
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.LaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollector;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index cde67f9..e6568f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 7e9cbc1..c2d139c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index aca4019..86f65dde 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index fc7bfb4..3beac0b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 39c630b..b5bcd45 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 0505d37..87f3164 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 944adba..1fbfff9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index d4af381..94961cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 9d7477c..38b9d50 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -16,7 +16,7 @@
 package com.android.systemui.keyguard.ui
 
 import android.view.animation.Interpolator
-import com.android.app.animation.Interpolators.LINEAR
+import com.android.systemui.animation.Interpolators.LINEAR
 import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
 import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index c8d37a1..d96609c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -32,11 +32,11 @@
 import androidx.core.view.updateLayoutParams
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.Expandable
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.view.LaunchableLinearLayout
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.binder.IconViewBinder
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 2c9a9b3..8d6545a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
-import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
index c135786..f16827d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index c6187dd..bc9dc4f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index d3ea89c..a60665a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index 6845c55..ddce516 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 68810f9..df93d23 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
index e38abc2..37d956b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
@@ -21,9 +21,9 @@
 import android.text.format.DateUtils
 import androidx.annotation.UiThread
 import androidx.lifecycle.Observer
-import com.android.app.animation.Interpolators
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.media.controls.ui.SquigglyProgress
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
index b46ebb2..3669493 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
@@ -34,10 +34,10 @@
 import android.util.MathUtils
 import android.view.View
 import androidx.annotation.Keep
-import com.android.app.animation.Interpolators
 import com.android.internal.graphics.ColorUtils
 import com.android.internal.graphics.ColorUtils.blendARGB
 import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
 import org.xmlpull.v1.XmlPullParser
 
 private const val BACKGROUND_ANIM_DURATION = 370L
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
index 937a618..dd5c2bf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
@@ -35,9 +35,9 @@
 import android.util.AttributeSet
 import android.util.MathUtils.lerp
 import androidx.annotation.Keep
-import com.android.app.animation.Interpolators
 import com.android.internal.graphics.ColorUtils
 import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
 import org.xmlpull.v1.XmlPullParser
 
 private const val RIPPLE_ANIM_DURATION = 800L
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index f9d3094..40027a1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -72,7 +72,6 @@
 import androidx.annotation.UiThread;
 import androidx.constraintlayout.widget.ConstraintSet;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -83,6 +82,7 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.bluetooth.BroadcastDialogController;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.dagger.qualifiers.Background;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index fe8ebaf..49e1665 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -33,9 +33,9 @@
 import android.view.ViewGroup
 import android.view.ViewGroupOverlay
 import androidx.annotation.VisibleForTesting
-import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardViewController
 import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dreams.DreamOverlayStateController
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
index 583c626..e9b2cf2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
@@ -31,8 +31,8 @@
 import android.util.MathUtils.lerpInv
 import android.util.MathUtils.lerpInvSat
 import androidx.annotation.VisibleForTesting
-import com.android.app.animation.Interpolators
 import com.android.internal.graphics.ColorUtils
+import com.android.systemui.animation.Interpolators
 import kotlin.math.abs
 import kotlin.math.cos
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 77ff036..78082c3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -36,7 +36,7 @@
 import android.view.View.ACCESSIBILITY_LIVE_REGION_NONE
 import com.android.internal.widget.CachingIconView
 import com.android.systemui.R
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.ui.binder.TintedIconViewBinder
 import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 146b5f5..94f01b8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -58,11 +58,11 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.Utils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
 import com.android.systemui.navigationbar.buttons.ContextualButton;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
index 10084bd..0218016 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.navigationbar.buttons;
 
-import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.systemui.animation.Interpolators.LINEAR;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -24,6 +24,9 @@
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
 
+import com.android.systemui.Dependency;
+import com.android.systemui.assist.AssistManager;
+
 import java.util.ArrayList;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index ff22398..590efbb 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -48,10 +48,10 @@
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.util.LatencyTracker;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
 import com.android.systemui.settings.DisplayTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 463c79c..a7aac5a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -26,7 +26,7 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.app.animation.Interpolators;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QSTile;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 09cc2c5..d806afa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -18,10 +18,9 @@
 
 import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
 import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
+import static com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
-import static com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.content.res.Configuration;
@@ -44,10 +43,10 @@
 import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.LifecycleRegistry;
 
-import com.android.app.animation.Interpolators;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.dump.DumpManager;
@@ -120,6 +119,7 @@
     private final QSLogger mLogger;
     private final FooterActionsController mFooterActionsController;
     private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
+    private final FooterActionsViewBinder mFooterActionsViewBinder;
     private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner;
     private boolean mShowCollapsedOnKeyguard;
     private boolean mLastKeyguardAndExpanded;
@@ -177,6 +177,7 @@
             DumpManager dumpManager, QSLogger qsLogger,
             FooterActionsController footerActionsController,
             FooterActionsViewModel.Factory footerActionsViewModelFactory,
+            FooterActionsViewBinder footerActionsViewBinder,
             LargeScreenShadeInterpolator largeScreenShadeInterpolator,
             FeatureFlags featureFlags) {
         mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
@@ -193,6 +194,7 @@
         mDumpManager = dumpManager;
         mFooterActionsController = footerActionsController;
         mFooterActionsViewModelFactory = footerActionsViewModelFactory;
+        mFooterActionsViewBinder = footerActionsViewBinder;
         mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
     }
 
@@ -285,7 +287,7 @@
 
         if (!ComposeFacade.INSTANCE.isComposeAvailable()) {
             Log.d(TAG, "Binding the View implementation of the QS footer actions");
-            FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
+            mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
                     mListeningAndVisibilityLifecycleOwner);
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
index 1921586..9c9ad33 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -33,27 +33,27 @@
 import com.android.systemui.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.people.ui.view.PeopleViewBinder.bind
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import javax.inject.Inject
 import kotlin.math.roundToInt
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 
 /** A ViewBinder for [FooterActionsViewBinder]. */
-object FooterActionsViewBinder {
+@SysUISingleton
+class FooterActionsViewBinder @Inject constructor() {
     /** Create a view that can later be [bound][bind] to a [FooterActionsViewModel]. */
-    @JvmStatic
     fun create(context: Context): LinearLayout {
         return LayoutInflater.from(context).inflate(R.layout.footer_actions, /* root= */ null)
             as LinearLayout
     }
 
     /** Bind [view] to [viewModel]. */
-    @JvmStatic
     fun bind(
         view: LinearLayout,
         viewModel: FooterActionsViewModel,
@@ -98,6 +98,11 @@
         var previousForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null
         var previousUserSwitcher: FooterActionsButtonViewModel? = null
 
+        // Set the initial visibility on the View directly so that we don't briefly show it for a
+        // few frames before [viewModel.isVisible] is collected.
+        view.isInvisible = !viewModel.isVisible.value
+
+        // Listen for ViewModel updates when the View is attached.
         view.repeatWhenAttached {
             val attachedScope = this.lifecycleScope
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index 3a9098a..b3596a2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -64,7 +64,7 @@
      * the UI should still participate to the layout it is included in (i.e. in the View world it
      * should be INVISIBLE, not GONE).
      */
-    private val _isVisible = MutableStateFlow(true)
+    private val _isVisible = MutableStateFlow(false)
     val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
 
     /** The alpha the UI rendering this ViewModel should have. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
index 5ea1c0b..4b22edc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
@@ -23,15 +23,16 @@
 import android.util.Log;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
+import dagger.Lazy;
+
 import java.util.Optional;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /**
  * An implementation of the Recents interface which proxies to the OverviewProxyService.
  */
@@ -44,13 +45,15 @@
 
     private Handler mHandler;
     private final OverviewProxyService mOverviewProxyService;
+    private final ActivityStarter mActivityStarter;
 
     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
     @Inject
     public OverviewProxyRecentsImpl(Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
-            OverviewProxyService overviewProxyService) {
+            OverviewProxyService overviewProxyService, ActivityStarter activityStarter) {
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
         mOverviewProxyService = overviewProxyService;
+        mActivityStarter = activityStarter;
     }
 
     @Override
@@ -101,7 +104,7 @@
             final Optional<CentralSurfaces> centralSurfacesOptional =
                     mCentralSurfacesOptionalLazy.get();
             if (centralSurfacesOptional.map(CentralSurfaces::isKeyguardShowing).orElse(false)) {
-                centralSurfacesOptional.get().executeRunnableDismissingKeyguard(
+                mActivityStarter.executeRunnableDismissingKeyguard(
                         () -> mHandler.post(toggleRecents), null, true /* dismissShade */,
                         false /* afterKeyguardGone */,
                         true /* deferred */);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
index 4f5cb72..3aefcb3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
@@ -33,11 +33,9 @@
 import android.view.RemoteAnimationAdapter;
 import android.view.WindowManagerGlobal;
 
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-
-import java.util.Optional;
 
 import javax.inject.Inject;
 
@@ -48,20 +46,20 @@
 public class ActionProxyReceiver extends BroadcastReceiver {
     private static final String TAG = "ActionProxyReceiver";
 
-    private final CentralSurfaces mCentralSurfaces;
     private final ActivityManagerWrapper mActivityManagerWrapper;
     private final ScreenshotSmartActions mScreenshotSmartActions;
     private final DisplayTracker mDisplayTracker;
+    private final ActivityStarter mActivityStarter;
 
     @Inject
-    public ActionProxyReceiver(Optional<CentralSurfaces> centralSurfacesOptional,
-            ActivityManagerWrapper activityManagerWrapper,
+    public ActionProxyReceiver(ActivityManagerWrapper activityManagerWrapper,
             ScreenshotSmartActions screenshotSmartActions,
-            DisplayTracker displayTracker) {
-        mCentralSurfaces = centralSurfacesOptional.orElse(null);
+            DisplayTracker displayTracker,
+            ActivityStarter activityStarter) {
         mActivityManagerWrapper = activityManagerWrapper;
         mScreenshotSmartActions = screenshotSmartActions;
         mDisplayTracker = displayTracker;
+        mActivityStarter = activityStarter;
     }
 
     @Override
@@ -92,13 +90,9 @@
 
         };
 
-        if (mCentralSurfaces != null) {
-            mCentralSurfaces.executeRunnableDismissingKeyguard(startActivityRunnable, null,
-                    true /* dismissShade */, true /* afterKeyguardGone */,
-                    true /* deferred */);
-        } else {
-            startActivityRunnable.run();
-        }
+        mActivityStarter.executeRunnableDismissingKeyguard(startActivityRunnable, null,
+                true /* dismissShade */, true /* afterKeyguardGone */,
+                true /* deferred */);
 
         if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
             String actionType = Intent.ACTION_EDIT.equals(intent.getAction())
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index 4cb91e1..14e875d2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -21,53 +21,44 @@
 import androidx.lifecycle.LifecycleService
 import androidx.lifecycle.lifecycleScope
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.shade.ShadeExpansionStateManager
-import com.android.systemui.statusbar.phone.CentralSurfaces
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
-import java.util.Optional
-import javax.inject.Inject
 
-/**
- * Provides state from the main SystemUI process on behalf of the Screenshot process.
- */
-internal class ScreenshotProxyService @Inject constructor(
+/** Provides state from the main SystemUI process on behalf of the Screenshot process. */
+internal class ScreenshotProxyService
+@Inject
+constructor(
     private val mExpansionMgr: ShadeExpansionStateManager,
-    private val mCentralSurfacesOptional: Optional<CentralSurfaces>,
     @Main private val mMainDispatcher: CoroutineDispatcher,
+    private val activityStarter: ActivityStarter,
 ) : LifecycleService() {
 
-    private val mBinder: IBinder = object : IScreenshotProxy.Stub() {
-        /**
-         * @return true when the notification shade is partially or fully expanded.
-         */
-        override fun isNotificationShadeExpanded(): Boolean {
-            val expanded = !mExpansionMgr.isClosed()
-            Log.d(TAG, "isNotificationShadeExpanded(): $expanded")
-            return expanded
-        }
+    private val mBinder: IBinder =
+        object : IScreenshotProxy.Stub() {
+            /** @return true when the notification shade is partially or fully expanded. */
+            override fun isNotificationShadeExpanded(): Boolean {
+                val expanded = !mExpansionMgr.isClosed()
+                Log.d(TAG, "isNotificationShadeExpanded(): $expanded")
+                return expanded
+            }
 
-        override fun dismissKeyguard(callback: IOnDoneCallback) {
-            lifecycleScope.launch {
-                executeAfterDismissing(callback)
+            override fun dismissKeyguard(callback: IOnDoneCallback) {
+                lifecycleScope.launch { executeAfterDismissing(callback) }
             }
         }
-    }
 
     private suspend fun executeAfterDismissing(callback: IOnDoneCallback) =
         withContext(mMainDispatcher) {
-            mCentralSurfacesOptional.ifPresentOrElse(
-                    {
-                        it.executeRunnableDismissingKeyguard(
-                                Runnable {
-                                    callback.onDone(true)
-                                }, null,
-                                true /* dismissShade */, true /* afterKeyguardGone */,
-                                true /* deferred */
-                        )
-                    },
-                    { callback.onDone(false) }
+            activityStarter.executeRunnableDismissingKeyguard(
+                Runnable { callback.onDone(true) },
+                null,
+                true /* dismissShade */,
+                true /* afterKeyguardGone */,
+                true /* deferred */
             )
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index be92bd4..5117915 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -22,10 +22,10 @@
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
-import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
-import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
 import static com.android.systemui.classifier.Classifier.GENERIC;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -88,7 +88,6 @@
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
@@ -112,6 +111,7 @@
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.LaunchAnimator;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.classifier.Classifier;
@@ -4086,7 +4086,7 @@
 
         if (!mFalsingManager.isUnlockingDisabled() && qsFullyExpanded
                 && mFalsingCollector.shouldEnforceBouncer()) {
-            mCentralSurfaces.executeRunnableDismissingKeyguard(null, null,
+            mActivityStarter.executeRunnableDismissingKeyguard(null, null,
                     false, true, false);
         }
         if (DEBUG_DRAWABLE) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 7a79e85..ef14d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -50,7 +50,6 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
@@ -60,6 +59,7 @@
 import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 4131e7d..f0815e9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -34,10 +34,10 @@
 import android.widget.TextView
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.motion.widget.MotionLayout
-import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
 import com.android.systemui.Dumpable
 import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index c1ebf12..63179da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -18,8 +18,8 @@
 
 import android.view.View;
 
-import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
index 1a32d70..54b341f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
@@ -38,8 +38,8 @@
 import android.view.animation.Interpolator;
 import android.widget.ImageView;
 
-import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 142689e..ea5a1c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -335,6 +335,9 @@
             R.id.keyguard_indication_text_bottom);
         mInitialTextColorState = mTopIndicationView != null
                 ? mTopIndicationView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
+        if (mRotateTextViewController != null) {
+            mRotateTextViewController.destroy();
+        }
         mRotateTextViewController = new KeyguardIndicationRotateTextViewController(
                 mLockScreenIndicationView,
                 mExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 823bb35..9421524 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -16,7 +16,7 @@
 import android.util.MathUtils.lerp
 import android.view.View
 import android.view.animation.PathInterpolator
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
 import com.android.systemui.util.getColorWithAlpha
 import com.android.systemui.util.leak.RotationUtils
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 2258968..c5f64b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -18,7 +18,7 @@
 import com.android.systemui.ExpandHelper
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.biometrics.UdfpsKeyguardViewController
 import com.android.systemui.classifier.Classifier
 import com.android.systemui.classifier.FalsingCollector
@@ -26,6 +26,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.qs.QS
@@ -62,11 +63,12 @@
     private val mediaHierarchyManager: MediaHierarchyManager,
     private val scrimTransitionController: LockscreenShadeScrimTransitionController,
     private val keyguardTransitionControllerFactory:
-        LockscreenShadeKeyguardTransitionController.Factory,
+    LockscreenShadeKeyguardTransitionController.Factory,
     private val depthController: NotificationShadeDepthController,
     private val context: Context,
     private val splitShadeOverScrollerFactory: SplitShadeLockScreenOverScroller.Factory,
     private val singleShadeOverScrollerFactory: SingleShadeLockScreenOverScroller.Factory,
+    private val activityStarter: ActivityStarter,
     wakefulnessLifecycle: WakefulnessLifecycle,
     configurationController: ConfigurationController,
     falsingManager: FalsingManager,
@@ -305,7 +307,7 @@
             if (nsslController.isInLockedDownShade()) {
                 logger.logDraggedDownLockDownShade(startingChild)
                 statusBarStateController.setLeaveOpenOnKeyguardHide(true)
-                centralSurfaces.dismissKeyguardThenExecute(OnDismissAction {
+                activityStarter.dismissKeyguardThenExecute(OnDismissAction {
                     nextHideKeyguardNeedsNoAnimation = true
                     false
                 }, cancelRunnable, false /* afterKeyguardGone */)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index fb88a96..72ae16e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -44,9 +44,9 @@
 import android.view.View;
 import android.widget.ImageView;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index d37cbcc..8dc7842 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -33,7 +33,7 @@
 import androidx.dynamicanimation.animation.SpringAnimation
 import androidx.dynamicanimation.animation.SpringForce
 import com.android.systemui.Dumpable
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 5c3bacc..7eb63da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -35,10 +35,10 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index f9d4f1a..976924a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -31,7 +31,7 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
index f1e51e2..575f354 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
@@ -4,7 +4,7 @@
 import android.content.res.Configuration
 import android.util.MathUtils
 import com.android.systemui.R
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import dagger.assisted.Assisted
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
index 3d574ca..572c0e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
@@ -8,7 +8,7 @@
 import android.view.animation.PathInterpolator
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.R
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 91c08a0..7755003 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -52,10 +52,10 @@
 
 import androidx.core.graphics.ColorUtils;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.util.drawable.DrawableSize;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index d6a14604..79d01b4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -42,7 +42,6 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -50,6 +49,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index 67ab060..2fa27ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -25,8 +25,8 @@
 import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 
-import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.TransformState;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index eddb683..bfc4e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -26,7 +26,7 @@
 import android.widget.FrameLayout
 import com.android.internal.annotations.GuardedBy
 import com.android.systemui.R
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
index b09b9f4..0446165 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -21,8 +21,8 @@
 import android.view.View;
 import android.widget.ImageView;
 
-import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.notification.row.HybridNotificationView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
index 785e65d..c22dbf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
@@ -3,7 +3,7 @@
 import android.util.MathUtils
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.LaunchAnimator
 import kotlin.math.min
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
index 5a14200..c22cd1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -23,13 +23,13 @@
 import android.view.ViewGroup;
 import android.widget.TextView;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.widget.IMessagingLayout;
 import com.android.internal.widget.MessagingGroup;
 import com.android.internal.widget.MessagingImageMessage;
 import com.android.internal.widget.MessagingLinearLayout;
 import com.android.internal.widget.MessagingMessage;
 import com.android.internal.widget.MessagingPropertyAnimator;
+import com.android.systemui.animation.Interpolators;
 
 import java.util.ArrayList;
 import java.util.HashMap;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
index a045698..3fc7b13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
@@ -24,8 +24,8 @@
 import android.view.View;
 import android.widget.ImageView;
 
-import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 import java.util.function.Consumer;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 9ba2199..fe0b28d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -21,8 +21,8 @@
 import androidx.annotation.VisibleForTesting
 import androidx.core.animation.ObjectAnimator
 import com.android.systemui.Dumpable
-import com.android.app.animation.Interpolators
-import com.android.app.animation.InterpolatorsAndroidX
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.InterpolatorsAndroidX
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 57d20246..5d07cac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -24,7 +24,7 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
-import com.android.app.animation.Interpolators;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ViewState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index 90eb630..9f9fba4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -23,11 +23,11 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.widget.MessagingImageMessage;
 import com.android.internal.widget.MessagingPropertyAnimator;
 import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.ViewTransformationHelper;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
index 16f1a45..dc16274 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
@@ -22,7 +22,7 @@
 import android.view.View
 import android.view.ViewGroup
 import com.android.systemui.R
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 
 /**
  * Class to help with fading of view groups without fading one subview
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index f70d5e6..766ad88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -31,12 +31,12 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
 import com.android.settingslib.Utils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 2695410..e468a59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -64,7 +64,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -73,6 +72,7 @@
 import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.CallLayout;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 7a2bee9..b56bae1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -45,10 +45,10 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 9dbbc58..5edff5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -32,9 +32,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.app.animation.Interpolators;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.Roundable;
 import com.android.systemui.statusbar.notification.RoundableState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 9bc0333..f21db0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -25,7 +25,7 @@
 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
 
-import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.systemui.animation.Interpolators.FAST_OUT_SLOW_IN;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index 047db20..596bdc0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -33,9 +33,9 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index e6e6b99..0c4b092 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -49,6 +49,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserContextProvider;
@@ -73,8 +74,6 @@
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /**
  * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
  * closing guts, and keeping track of the currently exposed notification guts.
@@ -107,7 +106,6 @@
     private NotificationListContainer mListContainer;
     private OnSettingsClickListener mOnSettingsClickListener;
 
-    private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     private final Handler mMainHandler;
     private final Handler mBgHandler;
     private final Optional<BubblesManager> mBubblesManagerOptional;
@@ -121,10 +119,10 @@
     private final ShadeController mShadeController;
     private NotifGutsViewListener mGutsListener;
     private final HeadsUpManagerPhone mHeadsUpManagerPhone;
+    private final ActivityStarter mActivityStarter;
 
     @Inject
     public NotificationGutsManager(Context context,
-            Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             @Main Handler mainHandler,
             @Background Handler bgHandler,
             AccessibilityManager accessibilityManager,
@@ -144,9 +142,9 @@
             StatusBarStateController statusBarStateController,
             DeviceProvisionedController deviceProvisionedController,
             MetricsLogger metricsLogger,
-            HeadsUpManagerPhone headsUpManagerPhone) {
+            HeadsUpManagerPhone headsUpManagerPhone,
+            ActivityStarter activityStarter) {
         mContext = context;
-        mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
         mMainHandler = mainHandler;
         mBgHandler = bgHandler;
         mAccessibilityManager = accessibilityManager;
@@ -167,6 +165,7 @@
         mDeviceProvisionedController = deviceProvisionedController;
         mMetricsLogger = metricsLogger;
         mHeadsUpManagerPhone = headsUpManagerPhone;
+        mActivityStarter = activityStarter;
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
@@ -551,19 +550,15 @@
                             .setLeaveOpenOnKeyguardHide(true);
                 }
 
-                Optional<CentralSurfaces> centralSurfacesOptional =
-                        mCentralSurfacesOptionalLazy.get();
-                if (centralSurfacesOptional.isPresent()) {
-                    Runnable r = () -> mMainHandler.post(
-                            () -> openGutsInternal(view, x, y, menuItem));
-                    centralSurfacesOptional.get().executeRunnableDismissingKeyguard(
-                            r,
-                            null /* cancelAction */,
-                            false /* dismissShade */,
-                            true /* afterKeyguardGone */,
-                            true /* deferred */);
-                    return true;
-                }
+                Runnable r = () -> mMainHandler.post(
+                        () -> openGutsInternal(view, x, y, menuItem));
+                mActivityStarter.executeRunnableDismissingKeyguard(
+                        r,
+                        null /* cancelAction */,
+                        false /* dismissShade */,
+                        true /* afterKeyguardGone */,
+                        true /* deferred */);
+                return true;
                 /**
                  * When {@link CentralSurfaces} doesn't exist, falling through to call
                  * {@link #openGutsInternal(View,int,int,NotificationMenuRowPlugin.MenuItem)}.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 99a7755..8a50f2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -20,7 +20,7 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 
-import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.systemui.animation.Interpolators.FAST_OUT_SLOW_IN;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 5a129fc..bafc474 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -39,9 +39,9 @@
 import android.widget.FrameLayout;
 import android.widget.FrameLayout.LayoutParams;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index d5d7f75..5f4c926 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -45,11 +45,11 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index b24cec1..5aaf63f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -23,8 +23,8 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.animation.Interpolators;
 
 import java.util.function.Consumer;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 84fe9ef..9a777ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -34,10 +34,10 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.NotificationExpandButton;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.ViewTransformationHelper;
 import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index d73bbeb..7f3381c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -22,8 +22,8 @@
 import android.animation.ValueAnimator;
 import android.view.View;
 
-import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index 9a33a94..0b435fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -26,7 +26,7 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
-import com.android.app.animation.Interpolators;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.ShadeViewRefactor;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 24e8f39..3bc2066 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -75,7 +75,6 @@
 import android.widget.OverScroller;
 import android.widget.ScrollView;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -87,8 +86,10 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.CommandQueue;
@@ -319,6 +320,7 @@
     };
     private NotificationStackScrollLogger mLogger;
     private CentralSurfaces mCentralSurfaces;
+    private ActivityStarter mActivityStarter;
     private final int[] mTempInt2 = new int[2];
     private boolean mGenerateChildOrderChangedEvent;
     private final HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
@@ -4189,10 +4191,7 @@
             mCentralSurfaces.resetUserExpandedStates();
             clearTemporaryViews();
             clearUserLockedViews();
-            if (mSwipeHelper.isSwiping()) {
-                mSwipeHelper.resetSwipeState();
-                updateContinuousShadowDrawing();
-            }
+            cancelActiveSwipe();
         }
     }
 
@@ -4264,6 +4263,9 @@
             if (!mIsExpanded) {
                 mGroupExpansionManager.collapseGroups();
                 mExpandHelper.cancelImmediately();
+                if (!mIsExpansionChanging) {
+                    cancelActiveSwipe();
+                }
             }
             updateNotificationAnimationStates();
             updateChronometers();
@@ -4814,6 +4816,10 @@
         this.mCentralSurfaces = centralSurfaces;
     }
 
+    public void setActivityStarter(ActivityStarter activityStarter) {
+        mActivityStarter = activityStarter;
+    }
+
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     void requestAnimateEverything() {
         if (mIsExpanded && mAnimationsEnabled) {
@@ -5579,7 +5585,7 @@
             Intent intent = showHistory
                     ? new Intent(Settings.ACTION_NOTIFICATION_HISTORY)
                     : new Intent(Settings.ACTION_NOTIFICATION_SETTINGS);
-            mCentralSurfaces.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            mActivityStarter.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
         });
         setEmptyShadeView(view);
         updateEmptyShadeView(
@@ -6113,7 +6119,11 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
+    private void cancelActiveSwipe() {
+        mSwipeHelper.resetSwipeState();
+        updateContinuousShadowDrawing();
+    }
+
     void updateContinuousShadowDrawing() {
         boolean continuousShadowUpdate = mAnimationRunning
                 || mSwipeHelper.isSwiping();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index b69ce38..4751fb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -63,6 +63,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
@@ -198,6 +199,7 @@
     private final NotificationTargetsHelper mNotificationTargetsHelper;
     private final SecureSettings mSecureSettings;
     private final NotificationDismissibilityProvider mDismissibilityProvider;
+    private final ActivityStarter mActivityStarter;
 
     private View mLongPressedView;
 
@@ -487,7 +489,7 @@
                     mView.addSwipedOutView(view);
                     mFalsingCollector.onNotificationDismissed();
                     if (mFalsingCollector.shouldEnforceBouncer()) {
-                        mCentralSurfaces.executeRunnableDismissingKeyguard(
+                        mActivityStarter.executeRunnableDismissingKeyguard(
                                 null,
                                 null /* cancelAction */,
                                 false /* dismissShade */,
@@ -678,7 +680,8 @@
             FeatureFlags featureFlags,
             NotificationTargetsHelper notificationTargetsHelper,
             SecureSettings secureSettings,
-            NotificationDismissibilityProvider dismissibilityProvider) {
+            NotificationDismissibilityProvider dismissibilityProvider,
+            ActivityStarter activityStarter) {
         mView = view;
         mStackStateLogger = stackLogger;
         mLogger = logger;
@@ -724,6 +727,7 @@
         mNotificationTargetsHelper = notificationTargetsHelper;
         mSecureSettings = secureSettings;
         mDismissibilityProvider = dismissibilityProvider;
+        mActivityStarter = activityStarter;
         updateResources();
         setUpView();
     }
@@ -734,6 +738,7 @@
         mView.setLogger(mLogger);
         mView.setTouchHandler(new TouchHandler());
         mView.setCentralSurfaces(mCentralSurfaces);
+        mView.setActivityStarter(mActivityStarter);
         mView.setClearAllAnimationListener(this::onAnimationEnd);
         mView.setClearAllListener((selection) -> mUiEventLogger.log(
                 NotificationPanelEvent.fromSelection(selection)));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index f07dd00..ee72943 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -22,9 +22,9 @@
 import android.util.Property;
 import android.view.View;
 
-import com.android.app.animation.Interpolators;
 import com.android.keyguard.KeyguardSliceView;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.shared.clocks.AnimatableClockView;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarIconView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
index f4605be..d07da38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
@@ -26,9 +26,9 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
-import com.android.app.animation.Interpolators;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.NotificationFadeAware.FadeOptimizedNotification;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
new file mode 100644
index 0000000..d9dc887
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -0,0 +1,867 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.app.ActivityManager
+import android.app.ActivityOptions
+import android.app.ActivityTaskManager
+import android.app.PendingIntent
+import android.app.TaskStackBuilder
+import android.content.Context
+import android.content.Intent
+import android.os.RemoteException
+import android.os.UserHandle
+import android.provider.Settings
+import android.util.Log
+import android.view.RemoteAnimationAdapter
+import android.view.View
+import android.view.WindowManager
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityLaunchAnimator.PendingIntentStarter
+import com.android.systemui.animation.DelegateLaunchAnimatorController
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.camera.CameraIntents.Companion.isInsecureCameraIntent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.kotlin.getOrNull
+import dagger.Lazy
+import java.util.Optional
+import javax.inject.Inject
+
+/** Handles start activity logic in SystemUI. */
+@SysUISingleton
+class ActivityStarterImpl
+@Inject
+constructor(
+    private val centralSurfacesOptLazy: Lazy<Optional<CentralSurfaces>>,
+    private val assistManagerLazy: Lazy<AssistManager>,
+    private val dozeServiceHostLazy: Lazy<DozeServiceHost>,
+    private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
+    private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
+    private val shadeControllerLazy: Lazy<ShadeController>,
+    private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>,
+    private val activityLaunchAnimator: ActivityLaunchAnimator,
+    private val context: Context,
+    private val lockScreenUserManager: NotificationLockscreenUserManager,
+    private val statusBarWindowController: StatusBarWindowController,
+    private val wakefulnessLifecycle: WakefulnessLifecycle,
+    private val keyguardStateController: KeyguardStateController,
+    private val statusBarStateController: SysuiStatusBarStateController,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val deviceProvisionedController: DeviceProvisionedController,
+    private val userTracker: UserTracker,
+    private val activityIntentHelper: ActivityIntentHelper,
+    @Main private val mainExecutor: DelayableExecutor,
+) : ActivityStarter {
+    companion object {
+        const val TAG = "ActivityStarterImpl"
+    }
+
+    private val centralSurfaces: CentralSurfaces?
+        get() = centralSurfacesOptLazy.get().getOrNull()
+
+    private val activityStarterInternal = ActivityStarterInternal()
+
+    override fun startPendingIntentDismissingKeyguard(intent: PendingIntent) {
+        activityStarterInternal.startPendingIntentDismissingKeyguard(intent = intent)
+    }
+
+    override fun startPendingIntentDismissingKeyguard(
+        intent: PendingIntent,
+        intentSentUiThreadCallback: Runnable?,
+    ) {
+        activityStarterInternal.startPendingIntentDismissingKeyguard(
+            intent = intent,
+            intentSentUiThreadCallback = intentSentUiThreadCallback,
+        )
+    }
+
+    override fun startPendingIntentDismissingKeyguard(
+        intent: PendingIntent,
+        intentSentUiThreadCallback: Runnable?,
+        associatedView: View?,
+    ) {
+        activityStarterInternal.startPendingIntentDismissingKeyguard(
+            intent = intent,
+            intentSentUiThreadCallback = intentSentUiThreadCallback,
+            associatedView = associatedView,
+        )
+    }
+
+    override fun startPendingIntentDismissingKeyguard(
+        intent: PendingIntent,
+        intentSentUiThreadCallback: Runnable?,
+        animationController: ActivityLaunchAnimator.Controller?,
+    ) {
+        activityStarterInternal.startPendingIntentDismissingKeyguard(
+            intent = intent,
+            intentSentUiThreadCallback = intentSentUiThreadCallback,
+            animationController = animationController,
+        )
+    }
+
+    /**
+     * TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate
+     *   this.
+     */
+    override fun startActivity(intent: Intent, dismissShade: Boolean) {
+        activityStarterInternal.startActivityDismissingKeyguard(
+            intent = intent,
+            dismissShade = dismissShade,
+        )
+    }
+
+    /**
+     * TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate
+     *   this.
+     */
+    override fun startActivity(intent: Intent, onlyProvisioned: Boolean, dismissShade: Boolean) {
+        activityStarterInternal.startActivityDismissingKeyguard(
+            intent = intent,
+            onlyProvisioned = onlyProvisioned,
+            dismissShade = dismissShade,
+        )
+    }
+
+    /**
+     * TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate
+     *   this.
+     */
+    override fun startActivity(
+        intent: Intent,
+        dismissShade: Boolean,
+        callback: ActivityStarter.Callback?,
+    ) {
+        activityStarterInternal.startActivityDismissingKeyguard(
+            intent = intent,
+            dismissShade = dismissShade,
+            callback = callback,
+        )
+    }
+
+    /**
+     * TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate
+     *   this.
+     */
+    override fun startActivity(
+        intent: Intent,
+        onlyProvisioned: Boolean,
+        dismissShade: Boolean,
+        flags: Int,
+    ) {
+        activityStarterInternal.startActivityDismissingKeyguard(
+            intent = intent,
+            onlyProvisioned = onlyProvisioned,
+            dismissShade = dismissShade,
+            flags = flags,
+        )
+    }
+
+    override fun startActivity(
+        intent: Intent,
+        dismissShade: Boolean,
+        animationController: ActivityLaunchAnimator.Controller?,
+        showOverLockscreenWhenLocked: Boolean,
+    ) {
+        activityStarterInternal.startActivity(
+            intent = intent,
+            dismissShade = dismissShade,
+            animationController = animationController,
+            showOverLockscreenWhenLocked = showOverLockscreenWhenLocked,
+        )
+    }
+    override fun startActivity(
+        intent: Intent,
+        dismissShade: Boolean,
+        animationController: ActivityLaunchAnimator.Controller?,
+        showOverLockscreenWhenLocked: Boolean,
+        userHandle: UserHandle?,
+    ) {
+        activityStarterInternal.startActivity(
+            intent = intent,
+            dismissShade = dismissShade,
+            animationController = animationController,
+            showOverLockscreenWhenLocked = showOverLockscreenWhenLocked,
+            userHandle = userHandle,
+        )
+    }
+
+    override fun postStartActivityDismissingKeyguard(intent: PendingIntent) {
+        postOnUiThread {
+            activityStarterInternal.startPendingIntentDismissingKeyguard(
+                intent = intent,
+            )
+        }
+    }
+
+    override fun postStartActivityDismissingKeyguard(
+        intent: PendingIntent,
+        animationController: ActivityLaunchAnimator.Controller?
+    ) {
+        postOnUiThread {
+            activityStarterInternal.startPendingIntentDismissingKeyguard(
+                intent = intent,
+                animationController = animationController,
+            )
+        }
+    }
+
+    override fun postStartActivityDismissingKeyguard(intent: Intent, delay: Int) {
+        postOnUiThread(delay) {
+            activityStarterInternal.startActivityDismissingKeyguard(intent = intent)
+        }
+    }
+
+    override fun postStartActivityDismissingKeyguard(
+        intent: Intent,
+        delay: Int,
+        animationController: ActivityLaunchAnimator.Controller?,
+    ) {
+        postOnUiThread(delay) {
+            activityStarterInternal.startActivityDismissingKeyguard(
+                intent = intent,
+                animationController = animationController,
+            )
+        }
+    }
+
+    override fun postStartActivityDismissingKeyguard(
+        intent: Intent,
+        delay: Int,
+        animationController: ActivityLaunchAnimator.Controller?,
+        customMessage: String?,
+    ) {
+        postOnUiThread(delay) {
+            activityStarterInternal.startActivityDismissingKeyguard(
+                intent = intent,
+                animationController = animationController,
+                customMessage = customMessage,
+            )
+        }
+    }
+
+    override fun dismissKeyguardThenExecute(
+        action: OnDismissAction,
+        cancel: Runnable?,
+        afterKeyguardGone: Boolean,
+    ) {
+        activityStarterInternal.dismissKeyguardThenExecute(
+            action = action,
+            cancel = cancel,
+            afterKeyguardGone = afterKeyguardGone,
+        )
+    }
+
+    override fun dismissKeyguardThenExecute(
+        action: OnDismissAction,
+        cancel: Runnable?,
+        afterKeyguardGone: Boolean,
+        customMessage: String?,
+    ) {
+        activityStarterInternal.dismissKeyguardThenExecute(
+            action = action,
+            cancel = cancel,
+            afterKeyguardGone = afterKeyguardGone,
+            customMessage = customMessage,
+        )
+    }
+
+    override fun startActivityDismissingKeyguard(
+        intent: Intent,
+        onlyProvisioned: Boolean,
+        dismissShade: Boolean,
+        disallowEnterPictureInPictureWhileLaunching: Boolean,
+        callback: ActivityStarter.Callback?,
+        flags: Int,
+        animationController: ActivityLaunchAnimator.Controller?,
+        userHandle: UserHandle?,
+    ) {
+        activityStarterInternal.startActivityDismissingKeyguard(
+            intent = intent,
+            onlyProvisioned = onlyProvisioned,
+            dismissShade = dismissShade,
+            disallowEnterPictureInPictureWhileLaunching =
+                disallowEnterPictureInPictureWhileLaunching,
+            callback = callback,
+            flags = flags,
+            animationController = animationController,
+            userHandle = userHandle,
+        )
+    }
+
+    override fun executeRunnableDismissingKeyguard(
+        runnable: Runnable?,
+        cancelAction: Runnable?,
+        dismissShade: Boolean,
+        afterKeyguardGone: Boolean,
+        deferred: Boolean,
+    ) {
+        activityStarterInternal.executeRunnableDismissingKeyguard(
+            runnable = runnable,
+            cancelAction = cancelAction,
+            dismissShade = dismissShade,
+            afterKeyguardGone = afterKeyguardGone,
+            deferred = deferred,
+        )
+    }
+
+    override fun executeRunnableDismissingKeyguard(
+        runnable: Runnable?,
+        cancelAction: Runnable?,
+        dismissShade: Boolean,
+        afterKeyguardGone: Boolean,
+        deferred: Boolean,
+        willAnimateOnKeyguard: Boolean,
+        customMessage: String?,
+    ) {
+        activityStarterInternal.executeRunnableDismissingKeyguard(
+            runnable = runnable,
+            cancelAction = cancelAction,
+            dismissShade = dismissShade,
+            afterKeyguardGone = afterKeyguardGone,
+            deferred = deferred,
+            willAnimateOnKeyguard = willAnimateOnKeyguard,
+            customMessage = customMessage,
+        )
+    }
+
+    override fun postQSRunnableDismissingKeyguard(runnable: Runnable?) {
+        postOnUiThread {
+            statusBarStateController.setLeaveOpenOnKeyguardHide(true)
+            activityStarterInternal.executeRunnableDismissingKeyguard(
+                runnable = { runnable?.let { postOnUiThread(runnable = it) } },
+            )
+        }
+    }
+
+    private fun postOnUiThread(delay: Int = 0, runnable: Runnable) {
+        mainExecutor.executeDelayed(runnable, delay.toLong())
+    }
+
+    /**
+     * Encapsulates the activity logic for activity starter.
+     *
+     * Logic is duplicated in {@link CentralSurfacesImpl}
+     */
+    private inner class ActivityStarterInternal {
+        /** Starts an activity after dismissing keyguard. */
+        fun startActivityDismissingKeyguard(
+            intent: Intent,
+            onlyProvisioned: Boolean = false,
+            dismissShade: Boolean = false,
+            disallowEnterPictureInPictureWhileLaunching: Boolean = false,
+            callback: ActivityStarter.Callback? = null,
+            flags: Int = 0,
+            animationController: ActivityLaunchAnimator.Controller? = null,
+            userHandle: UserHandle? = null,
+            customMessage: String? = null,
+        ) {
+            val userHandle: UserHandle = userHandle ?: getActivityUserHandle(intent)
+
+            if (onlyProvisioned && !deviceProvisionedController.isDeviceProvisioned) return
+
+            val willLaunchResolverActivity: Boolean =
+                activityIntentHelper.wouldLaunchResolverActivity(
+                    intent,
+                    lockScreenUserManager.currentUserId
+                )
+
+            val animate =
+                animationController != null &&
+                    !willLaunchResolverActivity &&
+                    centralSurfaces?.shouldAnimateLaunch(true /* isActivityIntent */) == true
+            val animController =
+                wrapAnimationController(
+                    animationController = animationController,
+                    dismissShade = dismissShade,
+                    isLaunchForActivity = true,
+                )
+
+            // If we animate, we will dismiss the shade only once the animation is done. This is
+            // taken care of by the StatusBarLaunchAnimationController.
+            val dismissShadeDirectly = dismissShade && animController == null
+
+            val runnable = Runnable {
+                assistManagerLazy.get().hideAssist()
+                intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+                intent.addFlags(flags)
+                val result = intArrayOf(ActivityManager.START_CANCELED)
+                activityLaunchAnimator.startIntentWithAnimation(
+                    animController,
+                    animate,
+                    intent.getPackage()
+                ) { adapter: RemoteAnimationAdapter? ->
+                    val options =
+                        ActivityOptions(
+                            CentralSurfaces.getActivityOptions(centralSurfaces!!.displayId, adapter)
+                        )
+
+                    // We know that the intent of the caller is to dismiss the keyguard and
+                    // this runnable is called right after the keyguard is solved, so we tell
+                    // WM that we should dismiss it to avoid flickers when opening an activity
+                    // that can also be shown over the keyguard.
+                    options.setDismissKeyguard()
+                    options.setDisallowEnterPictureInPictureWhileLaunching(
+                        disallowEnterPictureInPictureWhileLaunching
+                    )
+                    if (isInsecureCameraIntent(intent)) {
+                        // Normally an activity will set it's requested rotation
+                        // animation on its window. However when launching an activity
+                        // causes the orientation to change this is too late. In these cases
+                        // the default animation is used. This doesn't look good for
+                        // the camera (as it rotates the camera contents out of sync
+                        // with physical reality). So, we ask the WindowManager to
+                        // force the cross fade animation if an orientation change
+                        // happens to occur during the launch.
+                        options.rotationAnimationHint =
+                            WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+                    }
+                    if (Settings.Panel.ACTION_VOLUME == intent.action) {
+                        // Settings Panel is implemented as activity(not a dialog), so
+                        // underlying app is paused and may enter picture-in-picture mode
+                        // as a result.
+                        // So we need to disable picture-in-picture mode here
+                        // if it is volume panel.
+                        options.setDisallowEnterPictureInPictureWhileLaunching(true)
+                    }
+                    try {
+                        result[0] =
+                            ActivityTaskManager.getService()
+                                .startActivityAsUser(
+                                    null,
+                                    context.basePackageName,
+                                    context.attributionTag,
+                                    intent,
+                                    intent.resolveTypeIfNeeded(context.contentResolver),
+                                    null,
+                                    null,
+                                    0,
+                                    Intent.FLAG_ACTIVITY_NEW_TASK,
+                                    null,
+                                    options.toBundle(),
+                                    userHandle.identifier,
+                                )
+                    } catch (e: RemoteException) {
+                        Log.w(TAG, "Unable to start activity", e)
+                    }
+                    result[0]
+                }
+                callback?.onActivityStarted(result[0])
+            }
+            val cancelRunnable = Runnable {
+                callback?.onActivityStarted(ActivityManager.START_CANCELED)
+            }
+            // Do not deferKeyguard when occluded because, when keyguard is occluded,
+            // we do not launch the activity until keyguard is done.
+            val occluded = (keyguardStateController.isShowing && keyguardStateController.isOccluded)
+            val deferred = !occluded
+            executeRunnableDismissingKeyguard(
+                runnable,
+                cancelRunnable,
+                dismissShadeDirectly,
+                willLaunchResolverActivity,
+                deferred,
+                animate,
+                customMessage,
+            )
+        }
+
+        /** Starts a pending intent after dismissing keyguard. */
+        fun startPendingIntentDismissingKeyguard(
+            intent: PendingIntent,
+            intentSentUiThreadCallback: Runnable? = null,
+            associatedView: View? = null,
+            animationController: ActivityLaunchAnimator.Controller? = null,
+        ) {
+            val animationController =
+                if (associatedView is ExpandableNotificationRow) {
+                    centralSurfaces?.getAnimatorControllerFromNotification(associatedView)
+                } else animationController
+
+            val willLaunchResolverActivity =
+                (intent.isActivity &&
+                    activityIntentHelper.wouldPendingLaunchResolverActivity(
+                        intent,
+                        lockScreenUserManager.currentUserId,
+                    ))
+
+            val animate =
+                !willLaunchResolverActivity &&
+                    animationController != null &&
+                    centralSurfaces?.shouldAnimateLaunch(intent.isActivity) == true
+
+            // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
+            // run the animation on the keyguard). The animation will take care of (instantly)
+            // collapsing the shade and hiding the keyguard once it is done.
+            val collapse = !animate
+            executeRunnableDismissingKeyguard(
+                runnable = {
+                    try {
+                        // We wrap animationCallback with a StatusBarLaunchAnimatorController so
+                        // that the shade is collapsed after the animation (or when it is cancelled,
+                        // aborted, etc).
+                        val controller: ActivityLaunchAnimator.Controller? =
+                            wrapAnimationController(
+                                animationController = animationController,
+                                dismissShade = true,
+                                isLaunchForActivity = intent.isActivity,
+                            )
+                        activityLaunchAnimator.startPendingIntentWithAnimation(
+                            controller,
+                            animate,
+                            intent.creatorPackage,
+                            object : PendingIntentStarter {
+                                override fun startPendingIntent(
+                                    animationAdapter: RemoteAnimationAdapter?
+                                ): Int {
+                                    val options =
+                                        ActivityOptions(
+                                            CentralSurfaces.getActivityOptions(
+                                                centralSurfaces!!.displayId,
+                                                animationAdapter
+                                            )
+                                        )
+                                    // TODO b/221255671: restrict this to only be set for
+                                    // notifications
+                                    options.isEligibleForLegacyPermissionPrompt = true
+                                    return intent.sendAndReturnResult(
+                                        null,
+                                        0,
+                                        null,
+                                        null,
+                                        null,
+                                        null,
+                                        options.toBundle()
+                                    )
+                                }
+                            },
+                        )
+                    } catch (e: PendingIntent.CanceledException) {
+                        // the stack trace isn't very helpful here.
+                        // Just log the exception message.
+                        Log.w(TAG, "Sending intent failed: $e")
+                        if (!collapse) {
+                            // executeRunnableDismissingKeyguard did not collapse for us already.
+                            centralSurfaces?.collapsePanelOnMainThread()
+                        }
+                        // TODO: Dismiss Keyguard.
+                    }
+                    if (intent.isActivity) {
+                        assistManagerLazy.get().hideAssist()
+                    }
+                    intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
+                },
+                afterKeyguardGone = willLaunchResolverActivity,
+                dismissShade = collapse,
+                willAnimateOnKeyguard = animate,
+            )
+        }
+
+        /** Starts an Activity. */
+        fun startActivity(
+            intent: Intent,
+            dismissShade: Boolean = false,
+            animationController: ActivityLaunchAnimator.Controller? = null,
+            showOverLockscreenWhenLocked: Boolean = false,
+            userHandle: UserHandle? = null,
+        ) {
+            val userHandle = userHandle ?: getActivityUserHandle(intent)
+            // Make sure that we dismiss the keyguard if it is directly dismissible or when we don't
+            // want to show the activity above it.
+            if (keyguardStateController.isUnlocked || !showOverLockscreenWhenLocked) {
+                startActivityDismissingKeyguard(
+                    intent = intent,
+                    onlyProvisioned = false,
+                    dismissShade = dismissShade,
+                    disallowEnterPictureInPictureWhileLaunching = false,
+                    callback = null,
+                    flags = 0,
+                    animationController = animationController,
+                    userHandle = userHandle,
+                )
+                return
+            }
+
+            val animate =
+                animationController != null &&
+                    centralSurfaces?.shouldAnimateLaunch(
+                        /* isActivityIntent= */ true,
+                        showOverLockscreenWhenLocked
+                    ) == true
+
+            var controller: ActivityLaunchAnimator.Controller? = null
+            if (animate) {
+                // Wrap the animation controller to dismiss the shade and set
+                // mIsLaunchingActivityOverLockscreen during the animation.
+                val delegate =
+                    wrapAnimationController(
+                        animationController = animationController,
+                        dismissShade = dismissShade,
+                        isLaunchForActivity = true,
+                    )
+                delegate?.let {
+                    controller =
+                        object : DelegateLaunchAnimatorController(delegate) {
+                            override fun onIntentStarted(willAnimate: Boolean) {
+                                delegate?.onIntentStarted(willAnimate)
+                                if (willAnimate) {
+                                    centralSurfaces?.setIsLaunchingActivityOverLockscreen(true)
+                                }
+                            }
+
+                            override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+                                super.onLaunchAnimationStart(isExpandingFullyAbove)
+
+                                // Double check that the keyguard is still showing and not going
+                                // away, but if so set the keyguard occluded. Typically, WM will let
+                                // KeyguardViewMediator know directly, but we're overriding that to
+                                // play the custom launch animation, so we need to take care of that
+                                // here. The unocclude animation is not overridden, so WM will call
+                                // KeyguardViewMediator's unocclude animation runner when the
+                                // activity is exited.
+                                if (
+                                    keyguardStateController.isShowing &&
+                                        !keyguardStateController.isKeyguardGoingAway
+                                ) {
+                                    Log.d(TAG, "Setting occluded = true in #startActivity.")
+                                    keyguardViewMediatorLazy
+                                        .get()
+                                        .setOccluded(true /* isOccluded */, true /* animate */)
+                                }
+                            }
+
+                            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+                                // Set mIsLaunchingActivityOverLockscreen to false before actually
+                                // finishing the animation so that we can assume that
+                                // mIsLaunchingActivityOverLockscreen being true means that we will
+                                // collapse the shade (or at least run the post collapse runnables)
+                                // later on.
+                                centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
+                                delegate?.onLaunchAnimationEnd(isExpandingFullyAbove)
+                            }
+
+                            override fun onLaunchAnimationCancelled(
+                                newKeyguardOccludedState: Boolean?
+                            ) {
+                                if (newKeyguardOccludedState != null) {
+                                    keyguardViewMediatorLazy
+                                        .get()
+                                        .setOccluded(newKeyguardOccludedState, false /* animate */)
+                                }
+
+                                // Set mIsLaunchingActivityOverLockscreen to false before actually
+                                // finishing the animation so that we can assume that
+                                // mIsLaunchingActivityOverLockscreen being true means that we will
+                                // collapse the shade (or at least run the // post collapse
+                                // runnables) later on.
+                                centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
+                                delegate.onLaunchAnimationCancelled(newKeyguardOccludedState)
+                            }
+                        }
+                }
+            } else if (dismissShade) {
+                // The animation will take care of dismissing the shade at the end of the animation.
+                // If we don't animate, collapse it directly.
+                centralSurfaces?.collapseShade()
+            }
+
+            // We should exit the dream to prevent the activity from starting below the
+            // dream.
+            if (keyguardUpdateMonitor.isDreaming) {
+                centralSurfaces?.awakenDreams()
+            }
+
+            activityLaunchAnimator.startIntentWithAnimation(
+                controller,
+                animate,
+                intent.getPackage(),
+                showOverLockscreenWhenLocked
+            ) { adapter: RemoteAnimationAdapter? ->
+                TaskStackBuilder.create(context)
+                    .addNextIntent(intent)
+                    .startActivities(
+                        CentralSurfaces.getActivityOptions(centralSurfaces!!.displayId, adapter),
+                        userHandle
+                    )
+            }
+        }
+
+        /** Executes an action after dismissing keyguard. */
+        fun dismissKeyguardThenExecute(
+            action: OnDismissAction,
+            cancel: Runnable? = null,
+            afterKeyguardGone: Boolean = false,
+            customMessage: String? = null,
+        ) {
+            if (
+                !action.willRunAnimationOnKeyguard() &&
+                    wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP &&
+                    keyguardStateController.canDismissLockScreen() &&
+                    !statusBarStateController.leaveOpenOnKeyguardHide() &&
+                    dozeServiceHostLazy.get().isPulsing
+            ) {
+                // Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a
+                // pulse.
+                // TODO: Factor this transition out of BiometricUnlockController.
+                biometricUnlockControllerLazy
+                    .get()
+                    .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+            }
+            if (keyguardStateController.isShowing) {
+                statusBarKeyguardViewManagerLazy
+                    .get()
+                    .dismissWithAction(action, cancel, afterKeyguardGone, customMessage)
+            } else {
+                // If the keyguard isn't showing but the device is dreaming, we should exit the
+                // dream.
+                if (keyguardUpdateMonitor.isDreaming) {
+                    centralSurfaces?.awakenDreams()
+                }
+                action.onDismiss()
+            }
+        }
+
+        /** Executes an action after dismissing keyguard. */
+        fun executeRunnableDismissingKeyguard(
+            runnable: Runnable? = null,
+            cancelAction: Runnable? = null,
+            dismissShade: Boolean = false,
+            afterKeyguardGone: Boolean = false,
+            deferred: Boolean = false,
+            willAnimateOnKeyguard: Boolean = false,
+            customMessage: String? = null,
+        ) {
+            val onDismissAction: OnDismissAction =
+                object : OnDismissAction {
+                    override fun onDismiss(): Boolean {
+                        if (runnable != null) {
+                            if (
+                                keyguardStateController.isShowing &&
+                                    keyguardStateController.isOccluded
+                            ) {
+                                statusBarKeyguardViewManagerLazy
+                                    .get()
+                                    .addAfterKeyguardGoneRunnable(runnable)
+                            } else {
+                                mainExecutor.execute(runnable)
+                            }
+                        }
+                        if (dismissShade) {
+                            if (
+                                shadeControllerLazy.get().isExpandedVisible &&
+                                    !statusBarKeyguardViewManagerLazy.get().isBouncerShowing
+                            ) {
+                                shadeControllerLazy.get().animateCollapseShadeDelayed()
+                            } else {
+                                // Do it after DismissAction has been processed to conserve the
+                                // needed ordering.
+                                postOnUiThread {
+                                    shadeControllerLazy.get().runPostCollapseRunnables()
+                                }
+                            }
+                        }
+                        return deferred
+                    }
+
+                    override fun willRunAnimationOnKeyguard(): Boolean {
+                        return willAnimateOnKeyguard
+                    }
+                }
+            dismissKeyguardThenExecute(
+                onDismissAction,
+                cancelAction,
+                afterKeyguardGone,
+                customMessage,
+            )
+        }
+
+        /**
+         * Return a [ActivityLaunchAnimator.Controller] wrapping `animationController` so that:
+         * - if it launches in the notification shade window and `dismissShade` is true, then the
+         *   shade will be instantly dismissed at the end of the animation.
+         * - if it launches in status bar window, it will make the status bar window match the
+         *   device size during the animation (that way, the animation won't be clipped by the
+         *   status bar size).
+         *
+         * @param animationController the controller that is wrapped and will drive the main
+         *   animation.
+         * @param dismissShade whether the notification shade will be dismissed at the end of the
+         *   animation. This is ignored if `animationController` is not animating in the shade
+         *   window.
+         * @param isLaunchForActivity whether the launch is for an activity.
+         */
+        private fun wrapAnimationController(
+            animationController: ActivityLaunchAnimator.Controller?,
+            dismissShade: Boolean,
+            isLaunchForActivity: Boolean,
+        ): ActivityLaunchAnimator.Controller? {
+            if (animationController == null) {
+                return null
+            }
+            val rootView = animationController.launchContainer.rootView
+            val controllerFromStatusBar: Optional<ActivityLaunchAnimator.Controller> =
+                statusBarWindowController.wrapAnimationControllerIfInStatusBar(
+                    rootView,
+                    animationController
+                )
+            if (controllerFromStatusBar.isPresent) {
+                return controllerFromStatusBar.get()
+            }
+
+            centralSurfaces?.let {
+                // If the view is not in the status bar, then we are animating a view in the shade.
+                // We have to make sure that we collapse it when the animation ends or is cancelled.
+                if (dismissShade) {
+                    return StatusBarLaunchAnimatorController(
+                        animationController,
+                        it,
+                        isLaunchForActivity
+                    )
+                }
+            }
+
+            return animationController
+        }
+
+        /** Retrieves the current user handle to start the Activity. */
+        private fun getActivityUserHandle(intent: Intent): UserHandle {
+            val packages: Array<String> =
+                context.resources.getStringArray(R.array.system_ui_packages)
+            for (pkg in packages) {
+                if (intent.component == null) break
+                if (pkg == intent.component.packageName) {
+                    return UserHandle(UserHandle.myUserId())
+                }
+            }
+            return userTracker.userHandle
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
index 4590712..9dce332 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -33,9 +33,9 @@
 import android.util.Log;
 import android.view.View;
 
-import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index f579d30..4e69069 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -45,7 +45,8 @@
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.RemoteTransitionAdapter;
 import com.android.systemui.navigationbar.NavigationBarView;
-import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.ActivityStarter.Callback;
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.shade.NotificationShadeWindowView;
@@ -53,11 +54,13 @@
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.LightRevealScrim;
 import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.util.Compile;
 
 import java.io.PrintWriter;
 
-public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwner {
+/** */
+public interface CentralSurfaces extends Dumpable, LifecycleOwner {
     boolean MULTIUSER_DEBUG = false;
     // Should match the values in PhoneWindowManager
     String SYSTEM_DIALOG_REASON_KEY = "reason";
@@ -230,29 +233,33 @@
 
     boolean isShadeDisabled();
 
-    @Override
+    /** Starts an activity. Please use ActivityStarter instead of using these methods directly. */
     void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade,
             int flags);
 
-    @Override
+    /** Starts an activity. Please use ActivityStarter instead of using these methods directly. */
     void startActivity(Intent intent, boolean dismissShade);
 
-    @Override
+    /** Starts an activity. Please use ActivityStarter instead of using these methods directly. */
+    void startActivity(Intent intent, boolean dismissShade,
+            @Nullable ActivityLaunchAnimator.Controller animationController);
+
+    /** Starts an activity. Please use ActivityStarter instead of using these methods directly. */
     void startActivity(Intent intent, boolean dismissShade,
             @Nullable ActivityLaunchAnimator.Controller animationController,
             boolean showOverLockscreenWhenLocked);
 
-    @Override
+    /** Starts an activity. Please use ActivityStarter instead of using these methods directly. */
     void startActivity(Intent intent, boolean dismissShade,
             @Nullable ActivityLaunchAnimator.Controller animationController,
             boolean showOverLockscreenWhenLocked, UserHandle userHandle);
 
     boolean isLaunchingActivityOverLockscreen();
 
-    @Override
+    /** Starts an activity. Please use ActivityStarter instead of using these methods directly. */
     void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade);
 
-    @Override
+    /** Starts an activity. Please use ActivityStarter instead of using these methods directly. */
     void startActivity(Intent intent, boolean dismissShade, Callback callback);
 
     boolean isWakeUpComingFromTouch();
@@ -315,19 +322,34 @@
 
     float getDisplayHeight();
 
+    /** Starts an activity intent that dismisses keyguard.
+     *
+     * Please use ActivityStarter instead of using these methods directly.
+     */
     void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned,
             boolean dismissShade, int flags);
 
+    /** Starts an activity intent that dismisses keyguard.
+     *
+     * Please use ActivityStarter instead of using these methods directly.
+     */
     void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned,
             boolean dismissShade);
 
+    /** Starts an activity intent that dismisses keyguard.
+     *
+     * Please use ActivityStarter instead of using these methods directly.
+     */
     void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned,
             boolean dismissShade, boolean disallowEnterPictureInPictureWhileLaunching,
             Callback callback, int flags,
             @Nullable ActivityLaunchAnimator.Controller animationController,
             UserHandle userHandle);
 
-    /** Starts an activity intent that dismisses keyguard. */
+    /** Starts an activity intent that dismisses keyguard.
+     *
+     * Please use ActivityStarter instead of using these methods directly.
+     */
     void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned,
             boolean dismissShade, boolean disallowEnterPictureInPictureWhileLaunching,
             Callback callback, int flags,
@@ -352,29 +374,70 @@
 
     void resetUserExpandedStates();
 
-    @Override
+    /**
+     * Dismisses Keyguard and executes an action afterwards.
+     *
+     * Please use ActivityStarter instead of using these methods directly.
+     */
     void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction,
             boolean afterKeyguardGone);
 
+    /**
+     * Dismisses Keyguard and executes an action afterwards.
+     *
+     * Please use ActivityStarter instead of using these methods directly.
+     */
+    void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction,
+            boolean afterKeyguardGone, @Nullable String customMessage);
+
     void setLockscreenUser(int newUserId);
 
-    @Override
+    /**
+     * Starts a QS runnable on the main thread and dismisses keyguard.
+     *
+     * Please use ActivityStarter instead of using these methods directly.
+     */
     void postQSRunnableDismissingKeyguard(Runnable runnable);
 
-    @Override
+    /**
+     * Starts an activity on the main thread with a delay.
+     *
+     * Please use ActivityStarter instead of using these methods directly.
+     */
     void postStartActivityDismissingKeyguard(PendingIntent intent);
 
-    @Override
+    /**
+     * Starts an activity on the main thread with a delay.
+     *
+     * Please use ActivityStarter instead of using these methods directly.
+     */
     void postStartActivityDismissingKeyguard(PendingIntent intent,
             @Nullable ActivityLaunchAnimator.Controller animationController);
 
-    @Override
+    /**
+     * Starts an activity on the main thread with a delay.
+     *
+     * Please use ActivityStarter instead of using these methods directly.
+     */
     void postStartActivityDismissingKeyguard(Intent intent, int delay);
 
-    @Override
+    /**
+     * Starts an activity on the main thread with a delay.
+     *
+     * Please use ActivityStarter instead of using these methods directly.
+     */
     void postStartActivityDismissingKeyguard(Intent intent, int delay,
             @Nullable ActivityLaunchAnimator.Controller animationController);
 
+    /**
+     * Starts an activity on the main thread with a delay.
+     *
+     * Please use ActivityStarter instead of using these methods directly.
+     */
+    void postStartActivityDismissingKeyguard(Intent intent, int delay,
+            @Nullable ActivityLaunchAnimator.Controller animationController,
+            @Nullable String customMessage);
+
     void showKeyguard();
 
     boolean hideKeyguard();
@@ -468,18 +531,14 @@
 
     void awakenDreams();
 
-    @Override
     void startPendingIntentDismissingKeyguard(PendingIntent intent);
 
-    @Override
     void startPendingIntentDismissingKeyguard(
             PendingIntent intent, @Nullable Runnable intentSentUiThreadCallback);
 
-    @Override
     void startPendingIntentDismissingKeyguard(PendingIntent intent,
             Runnable intentSentUiThreadCallback, View associatedView);
 
-    @Override
     void startPendingIntentDismissingKeyguard(
             PendingIntent intent, @Nullable Runnable intentSentUiThreadCallback,
             @Nullable ActivityLaunchAnimator.Controller animationController);
@@ -554,4 +613,15 @@
             mDeviceId = deviceId;
         }
     }
+
+    /**
+     * Sets launching activity over LS state in central surfaces.
+     */
+    void setIsLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen);
+
+    /**
+     * Gets an animation controller from a notification row.
+     */
+    ActivityLaunchAnimator.Controller getAnimatorControllerFromNotification(
+            ExpandableNotificationRow associatedView);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index c0a7a34..37e77766 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -50,6 +50,7 @@
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.settings.UserTracker;
@@ -102,6 +103,7 @@
     private final boolean mVibrateOnOpening;
     private final VibrationEffect mCameraLaunchGestureVibrationEffect;
     private final SystemBarAttributesListener mSystemBarAttributesListener;
+    private final ActivityStarter mActivityStarter;
     private final Lazy<CameraLauncher> mCameraLauncherLazy;
     private final QuickSettingsController mQsController;
     private final QSHost mQSHost;
@@ -138,7 +140,8 @@
             SystemBarAttributesListener systemBarAttributesListener,
             Lazy<CameraLauncher> cameraLauncherLazy,
             UserTracker userTracker,
-            QSHost qsHost) {
+            QSHost qsHost,
+            ActivityStarter activityStarter) {
         mCentralSurfaces = centralSurfaces;
         mQsController = quickSettingsController;
         mContext = context;
@@ -170,6 +173,7 @@
         mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
                 mVibratorOptional, resources);
         mSystemBarAttributesListener = systemBarAttributesListener;
+        mActivityStarter = activityStarter;
     }
 
     @Override
@@ -375,7 +379,7 @@
         if (!mKeyguardStateController.isShowing()) {
             final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext);
             cameraIntent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source);
-            mCentralSurfaces.startActivityDismissingKeyguard(cameraIntent,
+            mActivityStarter.startActivityDismissingKeyguard(cameraIntent,
                     false /* onlyProvisioned */, true /* dismissShade */,
                     true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
                     null /* animationController */, mUserTracker.getUserHandle());
@@ -432,7 +436,7 @@
         // app-side haptic experimentation.
 
         if (!mKeyguardStateController.isShowing()) {
-            mCentralSurfaces.startActivityDismissingKeyguard(emergencyIntent,
+            mActivityStarter.startActivityDismissingKeyguard(emergencyIntent,
                     false /* onlyProvisioned */, true /* dismissShade */,
                     true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
                     null /* animationController */, mUserTracker.getUserHandle());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 2387495..aa5aed7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -168,6 +168,8 @@
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.notetask.NoteTaskController;
+import com.android.systemui.plugins.ActivityStarter.Callback;
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.OverlayPlugin;
@@ -281,6 +283,9 @@
  * <b>If at all possible, please avoid adding additional code to this monstrous class! Our goal is
  * to break up this class into many small classes, and any code added here will slow down that goal.
  * </b>
+ *
+ * Note that ActivityStarter logic here is deprecated and should be added here as well as
+ * {@link ActivityStarterImpl}
  */
 @SysUISingleton
 public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@@ -1790,17 +1795,27 @@
         return (mDisabled1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade,
             int flags) {
         startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, flags);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void startActivity(Intent intent, boolean dismissShade) {
         startActivityDismissingKeyguard(intent, false /* onlyProvisioned */, dismissShade);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
+    @Override
+    public void startActivity(Intent intent, boolean dismissShade,
+            @androidx.annotation.Nullable ActivityLaunchAnimator.Controller animationController) {
+        startActivity(intent, dismissShade, animationController, false);
+    }
+
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void startActivity(Intent intent, boolean dismissShade,
             @Nullable ActivityLaunchAnimator.Controller animationController,
@@ -1809,6 +1824,7 @@
                 getActivityUserHandle(intent));
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void startActivity(Intent intent, boolean dismissShade,
             @Nullable ActivityLaunchAnimator.Controller animationController,
@@ -2406,6 +2422,7 @@
         return mDisplayId;
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
             boolean dismissShade, int flags) {
@@ -2414,12 +2431,14 @@
                 flags, null /* animationController */, getActivityUserHandle(intent));
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
             boolean dismissShade) {
         startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, 0);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned,
             boolean dismissShade, boolean disallowEnterPictureInPictureWhileLaunching,
@@ -2431,6 +2450,7 @@
                 userHandle, null /* customMessage */);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
             final boolean dismissShade, final boolean disallowEnterPictureInPictureWhileLaunching,
@@ -2541,6 +2561,8 @@
      *                     animation. This is ignored if {@code animationController} is not
      *                     animating in the shade window.
      * @param isLaunchForActivity whether the launch is for an activity.
+     *
+     * Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too.
      */
     @Nullable
     private ActivityLaunchAnimator.Controller wrapAnimationController(
@@ -2570,6 +2592,8 @@
         mStatusBarKeyguardViewManager.readyForKeyguardDone();
     }
 
+
+     /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void executeRunnableDismissingKeyguard(final Runnable runnable,
             final Runnable cancelAction,
@@ -2580,6 +2604,7 @@
                 deferred, false /* willAnimateOnKeyguard */, null /* customMessage */);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void executeRunnableDismissingKeyguard(final Runnable runnable,
             final Runnable cancelAction,
@@ -2690,16 +2715,19 @@
                 afterKeyguardGone /* afterKeyguardGone */);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     protected void dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone) {
         dismissKeyguardThenExecute(action, null /* cancelRunnable */, afterKeyguardGone);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction,
             boolean afterKeyguardGone) {
         dismissKeyguardThenExecute(action, cancelAction, afterKeyguardGone, null);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction,
             boolean afterKeyguardGone, String customMessage) {
@@ -2901,6 +2929,7 @@
                 | ((currentlyInsecure ? 1 : 0) << 12);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void postQSRunnableDismissingKeyguard(final Runnable runnable) {
         mMainExecutor.execute(() -> {
@@ -2910,11 +2939,13 @@
         });
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void postStartActivityDismissingKeyguard(PendingIntent intent) {
         postStartActivityDismissingKeyguard(intent, null /* animationController */);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void postStartActivityDismissingKeyguard(final PendingIntent intent,
             @Nullable ActivityLaunchAnimator.Controller animationController) {
@@ -2922,11 +2953,13 @@
                 null /* intentSentUiThreadCallback */, animationController));
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void postStartActivityDismissingKeyguard(final Intent intent, int delay) {
         postStartActivityDismissingKeyguard(intent, delay, null /* animationController */);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void postStartActivityDismissingKeyguard(Intent intent, int delay,
             @Nullable ActivityLaunchAnimator.Controller animationController) {
@@ -2934,6 +2967,7 @@
                 null /* customMessage */);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void postStartActivityDismissingKeyguard(Intent intent, int delay,
             @Nullable ActivityLaunchAnimator.Controller animationController,
@@ -3640,9 +3674,6 @@
                         /* wakingUp= */ true,
                         mShouldDelayWakeUpAnimation);
 
-                if (!mKeyguardBypassController.getBypassEnabled()) {
-                    mHeadsUpManager.releaseAllImmediately();
-                }
                 updateVisibleToUser();
                 updateIsKeyguard();
                 mDozeServiceHost.stopDozing();
@@ -4084,11 +4115,13 @@
         dismissKeyguardThenExecute(onDismissAction, afterKeyguardGone);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
         startPendingIntentDismissingKeyguard(intent, null);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void startPendingIntentDismissingKeyguard(
             final PendingIntent intent, @Nullable final Runnable intentSentUiThreadCallback) {
@@ -4096,6 +4129,7 @@
                 (ActivityLaunchAnimator.Controller) null);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void startPendingIntentDismissingKeyguard(PendingIntent intent,
             Runnable intentSentUiThreadCallback, View associatedView) {
@@ -4109,6 +4143,7 @@
                 animationController);
     }
 
+    /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */
     @Override
     public void startPendingIntentDismissingKeyguard(
             final PendingIntent intent, @Nullable final Runnable intentSentUiThreadCallback,
@@ -4532,6 +4567,8 @@
      *  launched as user of the current process.
      * @param intent
      * @return UserHandle
+     *
+     * Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too.
      */
     private UserHandle getActivityUserHandle(Intent intent) {
         String[] packages = mContext.getResources().getStringArray(R.array.system_ui_packages);
@@ -4554,4 +4591,15 @@
                 && mBiometricUnlockController.getMode()
                 != BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
     }
+
+    @Override
+    public void setIsLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen) {
+        mIsLaunchingActivityOverLockscreen = isLaunchingActivityOverLockscreen;
+    }
+
+    @Override
+    public ActivityLaunchAnimator.Controller getAnimatorControllerFromNotification(
+            ExpandableNotificationRow associatedView) {
+        return mNotificationAnimationProvider.getAnimatorController(associatedView);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index c1859b2..90a6d0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -23,10 +23,10 @@
 import android.content.res.Resources;
 import android.util.MathUtils;
 
-import com.android.app.animation.Interpolators;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index 61c1cc8..9d30cb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -30,9 +30,9 @@
 
 import androidx.annotation.StyleRes;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.keyguard.KeyguardIndication;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 720eeba..13566ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -43,9 +43,9 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 5232fb6..e835c5ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -37,12 +37,12 @@
 import androidx.core.animation.AnimatorListenerAdapter;
 import androidx.core.animation.ValueAnimator;
 
-import com.android.app.animation.InterpolatorsAndroidX;
 import com.android.keyguard.CarrierTextController;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.R;
+import com.android.systemui.animation.InterpolatorsAndroidX;
 import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.log.LogLevel;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index 7bc4fc3..6bf5443 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -24,21 +24,21 @@
 import android.util.MathUtils;
 import android.util.TimeUtils;
 
-import com.android.app.animation.Interpolators;
 import com.android.systemui.Dumpable;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
 import dagger.assisted.AssistedInject;
 
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-
 /**
  * Class to control all aspects about light bar changes.
  */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 46a2457..cc4f901 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -31,9 +31,9 @@
 import android.util.SparseArray;
 import android.view.ViewTreeObserver.OnPreDrawListener;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 560ea8a..55dc188 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -15,11 +15,11 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.collection.ArrayMap;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index bef422c..006a029d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -36,10 +36,10 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.collection.ArrayMap;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
index 07a6d0a..5e5317d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
@@ -29,7 +29,7 @@
 import android.view.animation.AnimationUtils;
 import android.widget.Button;
 
-import com.android.app.animation.Interpolators;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
 
 public class SettingsButton extends AlphaOptimizedImageView {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f7646d7..89dddbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -68,6 +68,7 @@
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -284,6 +285,7 @@
     private boolean mIsBackAnimationEnabled;
     private final boolean mUdfpsNewTouchDetectionEnabled;
     private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
+    private final ActivityStarter mActivityStarter;
 
     private OnDismissAction mAfterKeyguardGoneAction;
     private Runnable mKeyguardGoneCancelAction;
@@ -339,7 +341,8 @@
             PrimaryBouncerInteractor primaryBouncerInteractor,
             BouncerView primaryBouncerView,
             AlternateBouncerInteractor alternateBouncerInteractor,
-            UdfpsOverlayInteractor udfpsOverlayInteractor
+            UdfpsOverlayInteractor udfpsOverlayInteractor,
+            ActivityStarter activityStarter
     ) {
         mContext = context;
         mViewMediatorCallback = callback;
@@ -367,6 +370,7 @@
                 featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM);
         mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION);
         mUdfpsOverlayInteractor = udfpsOverlayInteractor;
+        mActivityStarter = activityStarter;
     }
 
     @Override
@@ -1006,7 +1010,13 @@
 
     @Override
     public void dismissAndCollapse() {
-        mCentralSurfaces.executeRunnableDismissingKeyguard(null, null, true, false, true);
+        mActivityStarter.executeRunnableDismissingKeyguard(
+                /* runnable= */ null,
+                /* cancelAction= */ null,
+                /* dismissShade= */ true,
+                /* afterKeyguardGone= */ false,
+                /* deferred= */ true
+        );
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index cdf6652..8fa803e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -15,7 +15,7 @@
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
 import com.android.systemui.DejankUtils
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.WakefulnessLifecycle
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 831d402..453dd1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -44,10 +44,10 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.core.animation.Animator;
 
-import com.android.app.animation.Interpolators;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
index e1ec94f..4dd63be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
@@ -24,9 +24,9 @@
 
 import androidx.core.graphics.ColorUtils;
 
-import com.android.app.animation.Interpolators;
 import com.android.keyguard.KeyguardConstants;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.qs.tiles.UserDetailItemView;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index 66b5256..928e011 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -31,7 +31,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import com.android.app.animation.Interpolators;
 import com.android.keyguard.KeyguardConstants;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -39,6 +38,7 @@
 import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
 import com.android.settingslib.drawable.CircleFramedDrawable;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
index 363b06a..850a4b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
@@ -21,11 +21,11 @@
 import android.util.Log;
 import android.view.View;
 
-import com.android.app.animation.Interpolators;
 import com.android.keyguard.AlphaOptimizedLinearLayout;
 import com.android.keyguard.KeyguardConstants;
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
+import com.android.systemui.animation.Interpolators;
 
 /**
  * The container for the user switcher on Keyguard.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index e311bad..403a7e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -73,7 +73,6 @@
 import androidx.core.animation.ObjectAnimator;
 import androidx.core.animation.ValueAnimator;
 
-import com.android.app.animation.InterpolatorsAndroidX;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.internal.logging.UiEvent;
@@ -81,6 +80,7 @@
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.animation.InterpolatorsAndroidX;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
index 46954b5..1612388 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
@@ -18,7 +18,7 @@
 
 import android.view.View
 import android.view.ViewGroup
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.ViewHierarchyAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.children
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 4fbbc89..e819f94 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -34,10 +34,10 @@
 import androidx.annotation.DimenRes
 import androidx.annotation.IdRes
 import androidx.annotation.VisibleForTesting
-import com.android.app.animation.Interpolators
 import com.android.internal.widget.CachingIconView
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Text.Companion.loadText
diff --git a/packages/SystemUI/src/com/android/systemui/util/ViewController.java b/packages/SystemUI/src/com/android/systemui/util/ViewController.java
index 0dd5788..1f118d1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/ViewController.java
@@ -109,6 +109,11 @@
         }
     }
 
+    /** Destroy ViewController, removing any listeners. */
+    public void destroy() {
+        mView.removeOnAttachStateChangeListener(mOnAttachStateListener);
+    }
+
     /**
      * Called when the view is attached and a call to {@link #init()} has been made in either order.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
index db4ab7e..5d80292 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
@@ -19,7 +19,7 @@
 import android.animation.ValueAnimator
 import android.graphics.PointF
 import android.util.MathUtils
-import com.android.app.animation.Interpolators
+import com.android.systemui.animation.Interpolators
 
 /**
  * The fraction after which we start fading in when going from a gone widget to a visible one
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 91078dc..77210b7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -109,7 +109,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -120,6 +119,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
new file mode 100644
index 0000000..2c680be
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.systemui.animation
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import java.lang.reflect.Modifier
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class InterpolatorsAndroidXTest : SysuiTestCase() {
+
+    @Test
+    fun testInterpolatorsAndInterpolatorsAndroidXPublicMethodsAreEqual() {
+        assertEquals(
+            Interpolators::class.java.getPublicMethods(),
+            InterpolatorsAndroidX::class.java.getPublicMethods()
+        )
+    }
+
+    @Test
+    fun testInterpolatorsAndInterpolatorsAndroidXPublicFieldsAreEqual() {
+        assertEquals(
+            Interpolators::class.java.getPublicFields(),
+            InterpolatorsAndroidX::class.java.getPublicFields()
+        )
+    }
+
+    private fun <T> Class<T>.getPublicMethods() =
+        declaredMethods
+            .filter { Modifier.isPublic(it.modifiers) }
+            .map { it.toString().replace(name, "") }
+            .toSet()
+
+    private fun <T> Class<T>.getPublicFields() =
+        fields.filter { Modifier.isPublic(it.modifiers) }.map { it.name }.toSet()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index da9ceb4..6ab54a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -19,7 +19,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import com.android.app.animation.Interpolators
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index f143c467..7b41605 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -376,6 +376,34 @@
         }
     }
 
+    @Test
+    public void testHomeControlsDoNotShowIfNotAvailable_featureEnabled() {
+        when(mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)).thenReturn(true);
+
+        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
+        stateController.setShouldShowComplications(true);
+
+        final Complication homeControlsComplication = Mockito.mock(Complication.class);
+        when(homeControlsComplication.getRequiredTypeAvailability())
+                .thenReturn(Complication.COMPLICATION_TYPE_HOME_CONTROLS);
+
+        stateController.addComplication(homeControlsComplication);
+
+        final DreamOverlayStateController.Callback callback =
+                Mockito.mock(DreamOverlayStateController.Callback.class);
+
+        stateController.addCallback(callback);
+        mExecutor.runAllReady();
+
+        // No home controls since it is not available.
+        assertThat(stateController.getComplications()).doesNotContain(homeControlsComplication);
+
+        stateController.setAvailableComplicationTypes(Complication.COMPLICATION_TYPE_HOME_CONTROLS
+                | Complication.COMPLICATION_TYPE_WEATHER);
+        mExecutor.runAllReady();
+        assertThat(stateController.getComplications()).contains(homeControlsComplication);
+    }
+
     private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) {
         return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
index c3b0e5226..d934f76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
@@ -23,9 +23,11 @@
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -88,6 +90,54 @@
     }
 
     @Test
+    public void onViewDetached_removesStatusBarStateListener() {
+        mController.onViewDetached();
+        verify(mStatusBarStateController).removeCallback(mStatusBarStateListener);
+    }
+
+    @Test
+    public void onViewDetached_removesAllScheduledIndications() {
+        // GIVEN show next indication runnable is set
+        final KeyguardIndicationRotateTextViewController.ShowNextIndication mockShowNextIndication =
+                mock(KeyguardIndicationRotateTextViewController.ShowNextIndication.class);
+        mController.mShowNextIndicationRunnable = mockShowNextIndication;
+
+        // WHEN the view is detached
+        mController.onViewDetached();
+
+        // THEN delayed execution is cancelled & runnable set to null
+        verify(mockShowNextIndication).cancelDelayedExecution();
+        assertNull(mController.mShowNextIndicationRunnable);
+    }
+
+    @Test
+    public void destroy_removesStatusBarStateListener() {
+        mController.destroy();
+        verify(mStatusBarStateController).removeCallback(mStatusBarStateListener);
+    }
+
+    @Test
+    public void destroy_removesOnAttachStateChangeListener() {
+        mController.destroy();
+        verify(mView).removeOnAttachStateChangeListener(any());
+    }
+
+    @Test
+    public void destroy_removesAllScheduledIndications() {
+        // GIVEN show next indication runnable is set
+        final KeyguardIndicationRotateTextViewController.ShowNextIndication mockShowNextIndication =
+                mock(KeyguardIndicationRotateTextViewController.ShowNextIndication.class);
+        mController.mShowNextIndicationRunnable = mockShowNextIndication;
+
+        // WHEN the controller is destroyed
+        mController.destroy();
+
+        // THEN delayed execution is cancelled & runnable set to null
+        verify(mockShowNextIndication).cancelDelayedExecution();
+        assertNull(mController.mShowNextIndicationRunnable);
+    }
+
+    @Test
     public void testInitialState_noIndication() {
         assertFalse(mController.hasIndications());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index a17b596..f4d2843 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -23,9 +23,9 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
-import com.android.app.animation.Interpolators
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
index 3efe382..a5b78b74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.keyguard.ui
 
 import androidx.test.filters.SmallTest
-import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 0ab0e2b..87ca9df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -106,6 +106,7 @@
     @Mock private QSSquishinessController mSquishinessController;
     @Mock private FooterActionsViewModel mFooterActionsViewModel;
     @Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
+    @Mock private FooterActionsViewBinder mFooterActionsViewBinder;
     @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
     @Mock private FeatureFlags mFeatureFlags;
     private View mQsFragmentView;
@@ -558,6 +559,7 @@
                 mock(QSLogger.class),
                 mock(FooterActionsController.class),
                 mFooterActionsViewModelFactory,
+                mFooterActionsViewBinder,
                 mLargeScreenShadeInterpolator,
                 mFeatureFlags);
     }
@@ -584,7 +586,7 @@
         when(mQsFragmentView.findViewById(R.id.header)).thenReturn(mHeader);
         when(mQsFragmentView.findViewById(android.R.id.edit)).thenReturn(new View(mContext));
         when(mQsFragmentView.findViewById(R.id.qs_footer_actions)).thenAnswer(
-                invocation -> FooterActionsViewBinder.create(mContext));
+                invocation -> new FooterActionsViewBinder().create(mContext));
     }
 
     private void setUpInflater() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 59f0d96..2cc6709 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -376,13 +376,13 @@
     @Test
     fun isVisible() {
         val underTest = utils.footerActionsViewModel()
-        assertThat(underTest.isVisible.value).isTrue()
-
-        underTest.onVisibilityChangeRequested(visible = false)
         assertThat(underTest.isVisible.value).isFalse()
 
         underTest.onVisibilityChangeRequested(visible = true)
         assertThat(underTest.isVisible.value).isTrue()
+
+        underTest.onVisibilityChangeRequested(visible = false)
+        assertThat(underTest.isVisible.value).isFalse()
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
index 7d58325..9ea30d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
@@ -39,9 +39,9 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -50,22 +50,20 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
 
-import java.util.Optional;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 public class ActionProxyReceiverTest extends SysuiTestCase {
-
-    @Mock
-    private CentralSurfaces mMockCentralSurfaces;
     @Mock
     private ActivityManagerWrapper mMockActivityManagerWrapper;
     @Mock
     private ScreenshotSmartActions mMockScreenshotSmartActions;
     @Mock
     private PendingIntent mMockPendingIntent;
+    @Mock
+    private ActivityStarter mActivityStarter;
 
     private Intent mIntent;
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
@@ -78,32 +76,19 @@
     }
 
     @Test
-    public void testPendingIntentSentWithoutStatusBar() throws PendingIntent.CanceledException {
-        ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(false);
-
-        actionProxyReceiver.onReceive(mContext, mIntent);
-
-        verify(mMockActivityManagerWrapper).closeSystemWindows(SYSTEM_DIALOG_REASON_SCREENSHOT);
-        verify(mMockCentralSurfaces, never()).executeRunnableDismissingKeyguard(
-                any(Runnable.class), any(Runnable.class), anyBoolean(), anyBoolean(), anyBoolean());
-        verify(mMockPendingIntent).send(
-                eq(mContext), anyInt(), isNull(), isNull(), isNull(), isNull(), any(Bundle.class));
-    }
-
-    @Test
     public void testPendingIntentSentWithStatusBar() throws PendingIntent.CanceledException {
-        ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(true);
+        ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver();
         // ensure that the pending intent call is passed through
         doAnswer((Answer<Object>) invocation -> {
             ((Runnable) invocation.getArgument(0)).run();
             return null;
-        }).when(mMockCentralSurfaces).executeRunnableDismissingKeyguard(
+        }).when(mActivityStarter).executeRunnableDismissingKeyguard(
                 any(Runnable.class), isNull(), anyBoolean(), anyBoolean(), anyBoolean());
 
         actionProxyReceiver.onReceive(mContext, mIntent);
 
         verify(mMockActivityManagerWrapper).closeSystemWindows(SYSTEM_DIALOG_REASON_SCREENSHOT);
-        verify(mMockCentralSurfaces).executeRunnableDismissingKeyguard(
+        verify(mActivityStarter).executeRunnableDismissingKeyguard(
                 any(Runnable.class), isNull(), eq(true), eq(true), eq(true));
         verify(mMockPendingIntent).send(
                 eq(mContext), anyInt(), isNull(), isNull(), isNull(), isNull(), any(Bundle.class));
@@ -111,7 +96,7 @@
 
     @Test
     public void testSmartActionsNotNotifiedByDefault() {
-        ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(true);
+        ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver();
 
         actionProxyReceiver.onReceive(mContext, mIntent);
 
@@ -122,7 +107,7 @@
 
     @Test
     public void testSmartActionsNotifiedIfEnabled() {
-        ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(true);
+        ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver();
         mIntent.putExtra(EXTRA_SMART_ACTIONS_ENABLED, true);
         String testId = "testID";
         mIntent.putExtra(EXTRA_ID, testId);
@@ -133,15 +118,12 @@
                 testId, ACTION_TYPE_SHARE, false, null);
     }
 
-    private ActionProxyReceiver constructActionProxyReceiver(boolean withStatusBar) {
-        if (withStatusBar) {
-            return new ActionProxyReceiver(
-                    Optional.of(mMockCentralSurfaces), mMockActivityManagerWrapper,
-                    mMockScreenshotSmartActions, mDisplayTracker);
-        } else {
-            return new ActionProxyReceiver(
-                    Optional.empty(), mMockActivityManagerWrapper, mMockScreenshotSmartActions,
-                    mDisplayTracker);
-        }
+    private ActionProxyReceiver constructActionProxyReceiver() {
+        return new ActionProxyReceiver(
+                mMockActivityManagerWrapper,
+                mMockScreenshotSmartActions,
+                mDisplayTracker,
+                mActivityStarter
+        );
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 20da8a6..9fe75ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -33,9 +33,9 @@
 import androidx.constraintlayout.motion.widget.MotionLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
-import com.android.app.animation.Interpolators
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 4438b98..f7fcab1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -325,6 +325,21 @@
     }
 
     @Test
+    public void createController_setIndicationAreaAgain_destroysPreviousRotateTextViewController() {
+        // GIVEN a controller with a mocked rotate text view controlller
+        final KeyguardIndicationRotateTextViewController mockedRotateTextViewController =
+                mock(KeyguardIndicationRotateTextViewController.class);
+        createController();
+        mController.mRotateTextViewController = mockedRotateTextViewController;
+
+        // WHEN a new indication area is set
+        mController.setIndicationArea(mIndicationArea);
+
+        // THEN the previous rotateTextViewController is destroyed
+        verify(mockedRotateTextViewController).destroy();
+    }
+
+    @Test
     public void createController_addsAlignmentListener() {
         createController();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index d017ffd..2106da8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -11,6 +11,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.shade.ShadeViewController
@@ -79,6 +80,7 @@
     @Mock lateinit var singleShadeOverScroller: SingleShadeLockScreenOverScroller
     @Mock lateinit var splitShadeOverScroller: SplitShadeLockScreenOverScroller
     @Mock lateinit var qsTransitionController: LockscreenShadeQsTransitionController
+    @Mock lateinit var activityStarter: ActivityStarter
     @Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
@@ -124,6 +126,7 @@
                         dumpManager)
                 },
                 qsTransitionControllerFactory = { qsTransitionController },
+                activityStarter = activityStarter,
             )
         transitionController.addCallback(transitionControllerCallback)
         whenever(nsslController.view).thenReturn(stackscroller)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index f0abf2f..a1168f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -32,9 +32,9 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
-import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ViewState;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 4bb2c87..3cefc99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -67,6 +67,7 @@
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserContextProvider;
@@ -80,7 +81,6 @@
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.wmshell.BubblesManager;
@@ -120,7 +120,6 @@
     @Mock private NotificationListContainer mNotificationListContainer;
     @Mock private OnSettingsClickListener mOnSettingsClickListener;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
-    @Mock private CentralSurfaces mCentralSurfaces;
     @Mock private AccessibilityManager mAccessibilityManager;
     @Mock private HighPriorityProvider mHighPriorityProvider;
     @Mock private INotificationManager mINotificationManager;
@@ -136,6 +135,7 @@
     @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private HeadsUpManagerPhone mHeadsUpManagerPhone;
+    @Mock private ActivityStarter mActivityStarter;
 
     @Before
     public void setUp() {
@@ -145,8 +145,8 @@
         mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
 
-        mGutsManager = new NotificationGutsManager(mContext,
-                () -> Optional.of(mCentralSurfaces), mHandler, mHandler, mAccessibilityManager,
+        mGutsManager = new NotificationGutsManager(mContext, mHandler, mHandler,
+                mAccessibilityManager,
                 mHighPriorityProvider, mINotificationManager,
                 mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
                 mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
@@ -156,7 +156,7 @@
                 mStatusBarStateController,
                 mDeviceProvisionedController,
                 mMetricsLogger,
-                mHeadsUpManagerPhone);
+                mHeadsUpManagerPhone, mActivityStarter);
         mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
                 mOnSettingsClickListener);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 420c7ae..6a0e3c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -48,6 +48,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -143,6 +144,7 @@
     @Mock private NotificationTargetsHelper mNotificationTargetsHelper;
     @Mock private SecureSettings mSecureSettings;
     @Mock private NotificationIconAreaController mIconAreaController;
+    @Mock private ActivityStarter mActivityStarter;
 
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -491,7 +493,8 @@
                 mFeatureFlags,
                 mNotificationTargetsHelper,
                 mSecureSettings,
-                mock(NotificationDismissibilityProvider.class)
+                mock(NotificationDismissibilityProvider.class),
+                mActivityStarter
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 7153e59..f771606 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -801,6 +801,34 @@
     }
 
     @Test
+    public void onShadeClosesWithAnimationWillResetSwipeState() {
+        // GIVEN shade is expanded
+        mStackScroller.setIsExpanded(true);
+        clearInvocations(mNotificationSwipeHelper);
+
+        // WHEN closing the shade with the animations
+        mStackScroller.onExpansionStarted();
+        mStackScroller.setIsExpanded(false);
+        mStackScroller.onExpansionStopped();
+
+        // VERIFY swipe is reset
+        verify(mNotificationSwipeHelper).resetSwipeState();
+    }
+
+    @Test
+    public void onShadeClosesWithoutAnimationWillResetSwipeState() {
+        // GIVEN shade is expanded
+        mStackScroller.setIsExpanded(true);
+        clearInvocations(mNotificationSwipeHelper);
+
+        // WHEN closing the shade without the animation
+        mStackScroller.setIsExpanded(false);
+
+        // VERIFY swipe is reset
+        verify(mNotificationSwipeHelper).resetSwipeState();
+    }
+
+    @Test
     public void testSplitShade_hasTopOverscroll() {
         mTestableResources
                 .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
new file mode 100644
index 0000000..b6b28c9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.RemoteException
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ActivityStarterImplTest : SysuiTestCase() {
+    @Mock private lateinit var centralSurfaces: CentralSurfaces
+    @Mock private lateinit var assistManager: AssistManager
+    @Mock private lateinit var dozeServiceHost: DozeServiceHost
+    @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+    @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator
+    @Mock private lateinit var shadeController: ShadeController
+    @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
+    @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager
+    @Mock private lateinit var statusBarWindowController: StatusBarWindowController
+    @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var activityIntentHelper: ActivityIntentHelper
+    private lateinit var underTest: ActivityStarterImpl
+    private val mainExecutor = FakeExecutor(FakeSystemClock())
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            ActivityStarterImpl(
+                Lazy { Optional.of(centralSurfaces) },
+                Lazy { assistManager },
+                Lazy { dozeServiceHost },
+                Lazy { biometricUnlockController },
+                Lazy { keyguardViewMediator },
+                Lazy { shadeController },
+                Lazy { statusBarKeyguardViewManager },
+                activityLaunchAnimator,
+                context,
+                lockScreenUserManager,
+                statusBarWindowController,
+                wakefulnessLifecycle,
+                keyguardStateController,
+                statusBarStateController,
+                keyguardUpdateMonitor,
+                deviceProvisionedController,
+                userTracker,
+                activityIntentHelper,
+                mainExecutor,
+            )
+    }
+
+    @Test
+    fun startPendingIntentDismissingKeyguard_keyguardShowing_dismissWithAction() {
+        val pendingIntent = mock(PendingIntent::class.java)
+        whenever(keyguardStateController.isShowing).thenReturn(true)
+        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+
+        underTest.startPendingIntentDismissingKeyguard(pendingIntent)
+
+        verify(statusBarKeyguardViewManager)
+            .dismissWithAction(any(OnDismissAction::class.java), eq(null), anyBoolean(), eq(null))
+    }
+
+    @Test
+    fun startPendingIntentDismissingKeyguard_associatedView_getAnimatorController() {
+        val pendingIntent = mock(PendingIntent::class.java)
+        val associatedView = mock(ExpandableNotificationRow::class.java)
+
+        underTest.startPendingIntentDismissingKeyguard(
+            intent = pendingIntent,
+            intentSentUiThreadCallback = null,
+            associatedView = associatedView,
+        )
+
+        verify(centralSurfaces).getAnimatorControllerFromNotification(associatedView)
+    }
+
+    @Test
+    fun startActivity_noUserHandleProvided_getUserHandle() {
+        val intent = mock(Intent::class.java)
+
+        underTest.startActivity(intent, false)
+
+        verify(userTracker).userHandle
+    }
+
+    @Test
+    fun postStartActivityDismissingKeyguard_pendingIntent_postsOnMain() {
+        val intent = mock(PendingIntent::class.java)
+
+        underTest.postStartActivityDismissingKeyguard(intent)
+
+        assertThat(mainExecutor.numPending()).isEqualTo(1)
+    }
+
+    @Test
+    fun postStartActivityDismissingKeyguard_intent_postsOnMain() {
+        val intent = mock(Intent::class.java)
+
+        underTest.postStartActivityDismissingKeyguard(intent, 0)
+
+        assertThat(mainExecutor.numPending()).isEqualTo(1)
+    }
+
+    @Test
+    fun dismissKeyguardThenExecute_startWakeAndUnlock() {
+        whenever(wakefulnessLifecycle.wakefulness)
+            .thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP)
+        whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
+        whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
+        whenever(dozeServiceHost.isPulsing).thenReturn(true)
+
+        underTest.dismissKeyguardThenExecute({ true }, {}, false)
+
+        verify(biometricUnlockController)
+            .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+    }
+
+    @Test
+    fun dismissKeyguardThenExecute_keyguardIsShowing_dismissWithAction() {
+        val customMessage = "Enter your pin."
+        whenever(keyguardStateController.isShowing).thenReturn(true)
+
+        underTest.dismissKeyguardThenExecute({ true }, {}, false, customMessage)
+
+        verify(statusBarKeyguardViewManager)
+            .dismissWithAction(
+                any(OnDismissAction::class.java),
+                any(Runnable::class.java),
+                eq(false),
+                eq(customMessage)
+            )
+    }
+
+    @Test
+    fun dismissKeyguardThenExecute_awakeDreams() {
+        val customMessage = "Enter your pin."
+        var dismissActionExecuted = false
+        whenever(keyguardStateController.isShowing).thenReturn(false)
+        whenever(keyguardUpdateMonitor.isDreaming).thenReturn(true)
+
+        underTest.dismissKeyguardThenExecute(
+            {
+                dismissActionExecuted = true
+                true
+            },
+            {},
+            false,
+            customMessage
+        )
+
+        verify(centralSurfaces).awakenDreams()
+        assertThat(dismissActionExecuted).isTrue()
+    }
+
+    @Test
+    @Throws(RemoteException::class)
+    fun executeRunnableDismissingKeyguard_dreaming_notShowing_awakenDreams() {
+        whenever(keyguardStateController.isShowing).thenReturn(false)
+        whenever(keyguardStateController.isOccluded).thenReturn(false)
+        whenever(keyguardUpdateMonitor.isDreaming).thenReturn(true)
+
+        underTest.executeRunnableDismissingKeyguard(
+            runnable = {},
+            cancelAction = null,
+            dismissShade = false,
+            afterKeyguardGone = false,
+            deferred = false
+        )
+
+        verify(centralSurfaces, times(1)).awakenDreams()
+    }
+
+    @Test
+    @Throws(RemoteException::class)
+    fun executeRunnableDismissingKeyguard_notDreaming_notShowing_doNotAwakenDreams() {
+        whenever(keyguardStateController.isShowing).thenReturn(false)
+        whenever(keyguardStateController.isOccluded).thenReturn(false)
+        whenever(keyguardUpdateMonitor.isDreaming).thenReturn(false)
+
+        underTest.executeRunnableDismissingKeyguard(
+            runnable = {},
+            cancelAction = null,
+            dismissShade = false,
+            afterKeyguardGone = false,
+            deferred = false
+        )
+
+        verify(centralSurfaces, never()).awakenDreams()
+    }
+
+    @Test
+    fun postQSRunnableDismissingKeyguard_leaveOpenStatusBarState() {
+        underTest.postQSRunnableDismissingKeyguard {}
+
+        assertThat(mainExecutor.numPending()).isEqualTo(1)
+        mainExecutor.runAllReady()
+        verify(statusBarStateController).setLeaveOpenOnKeyguardHide(true)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 872c560..3870d99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.CameraLauncher;
@@ -96,6 +97,7 @@
     @Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
     @Mock private UserTracker mUserTracker;
     @Mock private QSHost mQSHost;
+    @Mock private ActivityStarter mActivityStarter;
 
     CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
 
@@ -131,7 +133,8 @@
                 mSystemBarAttributesListener,
                 mCameraLauncherLazy,
                 mUserTracker,
-                mQSHost);
+                mQSHost,
+                mActivityStarter);
 
         when(mUserTracker.getUserHandle()).thenReturn(
                 UserHandle.of(ActivityManager.getCurrentUser()));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 4ff225c..6be0e2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -72,6 +72,7 @@
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.ShadeController;
@@ -129,6 +130,7 @@
     @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock private UdfpsOverlayInteractor mUdfpsOverlayInteractor;
+    @Mock private ActivityStarter mActivityStarter;
     @Mock private BouncerView mBouncerView;
     @Mock private BouncerViewDelegate mBouncerViewDelegate;
     @Mock private OnBackAnimationCallback mBouncerViewDelegateBackCallback;
@@ -192,7 +194,8 @@
                         mPrimaryBouncerInteractor,
                         mBouncerView,
                         mAlternateBouncerInteractor,
-                        mUdfpsOverlayInteractor) {
+                        mUdfpsOverlayInteractor,
+                        mActivityStarter) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -680,7 +683,8 @@
                         mPrimaryBouncerInteractor,
                         mBouncerView,
                         mAlternateBouncerInteractor,
-                        mUdfpsOverlayInteractor) {
+                        mUdfpsOverlayInteractor,
+                        mActivityStarter) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
index 0983041..5b431e7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
@@ -14,8 +14,6 @@
 
 package com.android.systemui.animation
 
-import com.android.app.animation.Interpolators
-
 /** A [LaunchAnimator] to be used in tests. */
 fun fakeLaunchAnimator(): LaunchAnimator {
     return LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
diff --git a/services/core/java/com/android/server/SoundTriggerInternal.java b/services/core/java/com/android/server/SoundTriggerInternal.java
index e6c1750..f184574 100644
--- a/services/core/java/com/android/server/SoundTriggerInternal.java
+++ b/services/core/java/com/android/server/SoundTriggerInternal.java
@@ -52,11 +52,6 @@
     // Enumerate possible STModules to attach to
     List<ModuleProperties> listModuleProperties(Identity originatorIdentity);
 
-    /**
-     * Dumps service-wide information.
-     */
-    void dump(FileDescriptor fd, PrintWriter pw, String[] args);
-
     interface Session {
         /**
          * Starts recognition for the given keyphraseId.
@@ -142,13 +137,14 @@
                 @ModelParams int modelParam);
 
         /**
+         * Invalidates the sound trigger session and clears any associated resources. Subsequent
+         * calls to this object will throw IllegalStateException.
+         */
+        void detach();
+
+        /**
          * Unloads (and stops if running) the given keyphraseId
          */
         int unloadKeyphraseModel(int keyphaseId);
-
-        /**
-         * Dumps session-wide information.
-         */
-        void dump(FileDescriptor fd, PrintWriter pw, String[] args);
     }
 }
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index a57dd40..7cdea8d 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -87,10 +87,11 @@
     private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3;  // unconfirmed
 
     private static final int MSG_CONFIGURE_SAFE_MEDIA = SAFE_MEDIA_VOLUME_MSG_START + 1;
-    private static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 2;
-    private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 3;
-    private static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 4;
-    /*package*/ static final int MSG_CSD_UPDATE_ATTENUATION = SAFE_MEDIA_VOLUME_MSG_START + 5;
+    private static final int MSG_CONFIGURE_SAFE_MEDIA_FORCED = SAFE_MEDIA_VOLUME_MSG_START + 2;
+    private static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 3;
+    private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 4;
+    private static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 5;
+    /*package*/ static final int MSG_CSD_UPDATE_ATTENUATION = SAFE_MEDIA_VOLUME_MSG_START + 6;
 
     private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
 
@@ -611,8 +612,7 @@
     }
 
     /*package*/ void configureSafeMedia(boolean forced, String caller) {
-        int msg = MSG_CONFIGURE_SAFE_MEDIA;
-
+        int msg = forced ? MSG_CONFIGURE_SAFE_MEDIA_FORCED : MSG_CONFIGURE_SAFE_MEDIA;
         mAudioHandler.removeMessages(msg);
 
         long time = 0;
@@ -622,7 +622,7 @@
         }
 
         mAudioHandler.sendMessageAtTime(
-                mAudioHandler.obtainMessage(msg, /*arg1=*/forced ? 1 : 0, /*arg2=*/0, caller),
+                mAudioHandler.obtainMessage(msg, /*arg1=*/0, /*arg2=*/0, caller),
                 time);
     }
 
@@ -664,8 +664,10 @@
 
     /*package*/ void handleMessage(Message msg) {
         switch (msg.what) {
+            case MSG_CONFIGURE_SAFE_MEDIA_FORCED:
             case MSG_CONFIGURE_SAFE_MEDIA:
-                onConfigureSafeMedia((msg.arg1 == 1), (String) msg.obj);
+                onConfigureSafeMedia((msg.what == MSG_CONFIGURE_SAFE_MEDIA_FORCED),
+                        (String) msg.obj);
                 break;
             case MSG_PERSIST_SAFE_VOLUME_STATE:
                 onPersistSafeVolumeState(msg.arg1);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index dab00d8..0b6d1c8 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -181,6 +181,19 @@
      */
     private String mThermalBrightnessThrottlingDataId;
 
+    /**
+     * Refresh rate range limitation based on the current device layout
+     */
+    @Nullable
+    private SurfaceControl.RefreshRateRange mLayoutLimitedRefreshRate;
+
+    /**
+     * RefreshRateRange limitation for @Temperature.ThrottlingStatus
+     */
+    @NonNull
+    private SparseArray<SurfaceControl.RefreshRateRange> mThermalRefreshRateThrottling =
+            new SparseArray<>();
+
     public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
         mDisplayId = displayId;
         mLayerStack = layerStack;
@@ -339,24 +352,24 @@
      */
     public void updateLayoutLimitedRefreshRateLocked(
             @Nullable SurfaceControl.RefreshRateRange layoutLimitedRefreshRate) {
-        if (!Objects.equals(layoutLimitedRefreshRate, mBaseDisplayInfo.layoutLimitedRefreshRate)) {
-            mBaseDisplayInfo.layoutLimitedRefreshRate = layoutLimitedRefreshRate;
-            mInfo.set(null);
+        if (!Objects.equals(layoutLimitedRefreshRate, mLayoutLimitedRefreshRate)) {
+            mLayoutLimitedRefreshRate = layoutLimitedRefreshRate;
+            mDirty = true;
         }
     }
     /**
-     * Updates refreshRateThermalThrottling
+     * Updates thermalRefreshRateThrottling
      *
-     * @param refreshRanges new refreshRateThermalThrottling ranges limited by layout or default
+     * @param refreshRanges new thermalRefreshRateThrottling ranges limited by layout or default
      */
     public void updateThermalRefreshRateThrottling(
             @Nullable SparseArray<SurfaceControl.RefreshRateRange> refreshRanges) {
         if (refreshRanges == null) {
             refreshRanges = new SparseArray<>();
         }
-        if (!mBaseDisplayInfo.refreshRateThermalThrottling.contentEquals(refreshRanges)) {
-            mBaseDisplayInfo.refreshRateThermalThrottling = refreshRanges;
-            mInfo.set(null);
+        if (!mThermalRefreshRateThrottling.contentEquals(refreshRanges)) {
+            mThermalRefreshRateThrottling = refreshRanges;
+            mDirty = true;
         }
     }
 
@@ -499,6 +512,9 @@
                 mBaseDisplayInfo.removeMode = Display.REMOVE_MODE_DESTROY_CONTENT;
             }
 
+            mBaseDisplayInfo.layoutLimitedRefreshRate = mLayoutLimitedRefreshRate;
+            mBaseDisplayInfo.thermalRefreshRateThrottling = mThermalRefreshRateThrottling;
+
             mPrimaryDisplayDeviceInfo = deviceInfo;
             mInfo.set(null);
             mDirty = false;
@@ -952,6 +968,8 @@
         pw.println("mDisplayGroupName=" + mDisplayGroupName);
         pw.println("mThermalBrightnessThrottlingDataId=" + mThermalBrightnessThrottlingDataId);
         pw.println("mLeadDisplayId=" + mLeadDisplayId);
+        pw.println("mLayoutLimitedRefreshRate=" + mLayoutLimitedRefreshRate);
+        pw.println("mThermalRefreshRateThrottling=" + mThermalRefreshRateThrottling);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 03b49f0..fd94be9 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -103,10 +103,6 @@
     private static final int MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED = 7;
     private static final int MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED = 8;
 
-    // Special ID used to indicate that given vote is to be applied globally, rather than to a
-    // specific display.
-    private static final int GLOBAL_ID = -1;
-
     private static final float FLOAT_TOLERANCE = RefreshRateRange.FLOAT_TOLERANCE;
 
     private final Object mLock = new Object();
@@ -129,10 +125,6 @@
     @Nullable
     private DisplayDeviceConfig mDefaultDisplayDeviceConfig;
 
-    // A map from the display ID to the collection of votes and their priority. The latter takes
-    // the form of another map from the priority to the vote itself so that each priority is
-    // guaranteed to have exactly one vote, which is also easily and efficiently replaceable.
-    private SparseArray<SparseArray<Vote>> mVotesByDisplay;
     // A map from the display ID to the supported modes on that display.
     private SparseArray<Display.Mode[]> mSupportedModesByDisplay;
     // A map from the display ID to the default mode of that display.
@@ -146,6 +138,8 @@
 
     private final boolean mSupportsFrameRateOverride;
 
+    private final VotesStorage mVotesStorage;
+
     /**
      * The allowed refresh rate switching type. This is used by SurfaceFlinger.
      */
@@ -161,7 +155,6 @@
         mContext = context;
         mHandler = new DisplayModeDirectorHandler(handler.getLooper());
         mInjector = injector;
-        mVotesByDisplay = new SparseArray<>();
         mSupportedModesByDisplay = new SparseArray<>();
         mDefaultModeByDisplay = new SparseArray<>();
         mAppRequestObserver = new AppRequestObserver();
@@ -171,15 +164,11 @@
         mBrightnessObserver = new BrightnessObserver(context, handler, injector);
         mDefaultDisplayDeviceConfig = null;
         mUdfpsObserver = new UdfpsObserver();
-        final BallotBox ballotBox = (displayId, priority, vote) -> {
-            synchronized (mLock) {
-                updateVoteLocked(displayId, priority, vote);
-            }
-        };
-        mDisplayObserver = new DisplayObserver(context, handler, ballotBox);
-        mSensorObserver = new SensorObserver(context, ballotBox, injector);
-        mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, ballotBox);
-        mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler(),
+        mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked);
+        mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage);
+        mSensorObserver = new SensorObserver(context, mVotesStorage, injector);
+        mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage);
+        mHbmObserver = new HbmObserver(injector, mVotesStorage, BackgroundThread.getHandler(),
                 mDeviceConfigDisplaySettings);
         mAlwaysRespectAppRequest = false;
         mSupportsFrameRateOverride = injector.supportsFrameRateOverride();
@@ -226,29 +215,7 @@
         mLoggingEnabled = loggingEnabled;
         mBrightnessObserver.setLoggingEnabled(loggingEnabled);
         mSkinThermalStatusObserver.setLoggingEnabled(loggingEnabled);
-    }
-
-    @NonNull
-    private SparseArray<Vote> getVotesLocked(int displayId) {
-        SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
-        final SparseArray<Vote> votes;
-        if (displayVotes != null) {
-            votes = displayVotes.clone();
-        } else {
-            votes = new SparseArray<>();
-        }
-
-        SparseArray<Vote> globalVotes = mVotesByDisplay.get(GLOBAL_ID);
-        if (globalVotes != null) {
-            for (int i = 0; i < globalVotes.size(); i++) {
-                int priority = globalVotes.keyAt(i);
-                if (votes.indexOfKey(priority) < 0) {
-                    votes.put(priority, globalVotes.valueAt(i));
-                }
-            }
-        }
-
-        return votes;
+        mVotesStorage.setLoggingEnabled(loggingEnabled);
     }
 
     private static final class VoteSummary {
@@ -407,7 +374,7 @@
     @NonNull
     public DesiredDisplayModeSpecs getDesiredDisplayModeSpecs(int displayId) {
         synchronized (mLock) {
-            SparseArray<Vote> votes = getVotesLocked(displayId);
+            SparseArray<Vote> votes = mVotesStorage.getVotes(displayId);
             Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
             Display.Mode defaultMode = mDefaultModeByDisplay.get(displayId);
             if (modes == null || defaultMode == null) {
@@ -780,10 +747,8 @@
     @VisibleForTesting
     @Nullable
     Vote getVote(int displayId, int priority) {
-        synchronized (mLock) {
-            SparseArray<Vote> votes = getVotesLocked(displayId);
-            return votes.get(priority);
-        }
+        SparseArray<Vote> votes = mVotesStorage.getVotes(displayId);
+        return votes.get(priority);
     }
 
     /**
@@ -806,18 +771,6 @@
                 final Display.Mode mode = mDefaultModeByDisplay.valueAt(i);
                 pw.println("    " + id + " -> " + mode);
             }
-            pw.println("  mVotesByDisplay:");
-            for (int i = 0; i < mVotesByDisplay.size(); i++) {
-                pw.println("    " + mVotesByDisplay.keyAt(i) + ":");
-                SparseArray<Vote> votes = mVotesByDisplay.valueAt(i);
-                for (int p = Vote.MAX_PRIORITY; p >= Vote.MIN_PRIORITY; p--) {
-                    Vote vote = votes.get(p);
-                    if (vote == null) {
-                        continue;
-                    }
-                    pw.println("      " + Vote.priorityToString(p) + " -> " + vote);
-                }
-            }
             pw.println("  mModeSwitchingType: " + switchingTypeToString(mModeSwitchingType));
             pw.println("  mAlwaysRespectAppRequest: " + mAlwaysRespectAppRequest);
             mSettingsObserver.dumpLocked(pw);
@@ -827,44 +780,10 @@
             mHbmObserver.dumpLocked(pw);
             mSkinThermalStatusObserver.dumpLocked(pw);
         }
-
+        mVotesStorage.dump(pw);
         mSensorObserver.dump(pw);
     }
 
-    private void updateVoteLocked(int priority, Vote vote) {
-        updateVoteLocked(GLOBAL_ID, priority, vote);
-    }
-
-    private void updateVoteLocked(int displayId, int priority, Vote vote) {
-        if (mLoggingEnabled) {
-            Slog.i(TAG, "updateVoteLocked(displayId=" + displayId
-                    + ", priority=" + Vote.priorityToString(priority)
-                    + ", vote=" + vote + ")");
-        }
-        if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) {
-            Slog.w(TAG, "Received a vote with an invalid priority, ignoring:"
-                    + " priority=" + Vote.priorityToString(priority)
-                    + ", vote=" + vote, new Throwable());
-            return;
-        }
-        final SparseArray<Vote> votes = getOrCreateVotesByDisplay(displayId);
-
-        if (vote != null) {
-            votes.put(priority, vote);
-        } else {
-            votes.remove(priority);
-        }
-
-        if (votes.size() == 0) {
-            if (mLoggingEnabled) {
-                Slog.i(TAG, "No votes left for display " + displayId + ", removing.");
-            }
-            mVotesByDisplay.remove(displayId);
-        }
-
-        notifyDesiredDisplayModeSpecsChangedLocked();
-    }
-
     @GuardedBy("mLock")
     private float getMaxRefreshRateLocked(int displayId) {
         Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
@@ -890,16 +809,6 @@
         }
     }
 
-    private SparseArray<Vote> getOrCreateVotesByDisplay(int displayId) {
-        if (mVotesByDisplay.indexOfKey(displayId) >= 0) {
-            return mVotesByDisplay.get(displayId);
-        } else {
-            SparseArray<Vote> votes = new SparseArray<>();
-            mVotesByDisplay.put(displayId, votes);
-            return votes;
-        }
-    }
-
     private static String switchingTypeToString(@DisplayManager.SwitchingType int type) {
         switch (type) {
             case DisplayManager.SWITCHING_TYPE_NONE:
@@ -927,7 +836,7 @@
 
     @VisibleForTesting
     void injectVotesByDisplay(SparseArray<SparseArray<Vote>> votesByDisplay) {
-        mVotesByDisplay = votesByDisplay;
+        mVotesStorage.injectVotesByDisplay(votesByDisplay);
     }
 
     @VisibleForTesting
@@ -1157,225 +1066,6 @@
     }
 
     @VisibleForTesting
-    static final class Vote {
-        // DEFAULT_RENDER_FRAME_RATE votes for render frame rate [0, DEFAULT]. As the lowest
-        // priority vote, it's overridden by all other considerations. It acts to set a default
-        // frame rate for a device.
-        public static final int PRIORITY_DEFAULT_RENDER_FRAME_RATE = 0;
-
-        // PRIORITY_FLICKER_REFRESH_RATE votes for a single refresh rate like [60,60], [90,90] or
-        // null. It is used to set a preferred refresh rate value in case the higher priority votes
-        // result is a range.
-        public static final int PRIORITY_FLICKER_REFRESH_RATE = 1;
-
-        // High-brightness-mode may need a specific range of refresh-rates to function properly.
-        public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 2;
-
-        // SETTING_MIN_RENDER_FRAME_RATE is used to propose a lower bound of the render frame rate.
-        // It votes [minRefreshRate, Float.POSITIVE_INFINITY]
-        public static final int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3;
-
-        // APP_REQUEST_RENDER_FRAME_RATE_RANGE is used to for internal apps to limit the render
-        // frame rate in certain cases, mostly to preserve power.
-        // @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate
-        // @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate
-        // It votes to [preferredMinRefreshRate, preferredMaxRefreshRate].
-        public static final int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 4;
-
-        // We split the app request into different priorities in case we can satisfy one desire
-        // without the other.
-
-        // Application can specify preferred refresh rate with below attrs.
-        // @see android.view.WindowManager.LayoutParams#preferredRefreshRate
-        // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId
-        //
-        // When the app specifies a LayoutParams#preferredDisplayModeId, in addition to the
-        // refresh rate, it also chooses a preferred size (resolution) as part of the selected
-        // mode id. The app preference is then translated to APP_REQUEST_BASE_MODE_REFRESH_RATE and
-        // optionally to APP_REQUEST_SIZE as well, if a mode id was selected.
-        // The system also forces some apps like denylisted app to run at a lower refresh rate.
-        // @see android.R.array#config_highRefreshRateBlacklist
-        //
-        // When summarizing the votes and filtering the allowed display modes, these votes determine
-        // which mode id should be the base mode id to be sent to SurfaceFlinger:
-        // - APP_REQUEST_BASE_MODE_REFRESH_RATE is used to validate the vote summary. If a summary
-        //   includes a base mode refresh rate, but it is not in the refresh rate range, then the
-        //   summary is considered invalid so we could drop a lower priority vote and try again.
-        // - APP_REQUEST_SIZE is used to filter out display modes of a different size.
-        //
-        // The preferred refresh rate is set on the main surface of the app outside of
-        // DisplayModeDirector.
-        // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded
-        public static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 5;
-        public static final int PRIORITY_APP_REQUEST_SIZE = 6;
-
-        // SETTING_PEAK_RENDER_FRAME_RATE has a high priority and will restrict the bounds of the
-        // rest of low priority voters. It votes [0, max(PEAK, MIN)]
-        public static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 7;
-
-        // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh
-        // rate to max value (same as for PRIORITY_UDFPS) on lock screen
-        public static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 8;
-
-        // For concurrent displays we want to limit refresh rate on all displays
-        public static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 9;
-
-        // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if
-        // Settings.Global.LOW_POWER_MODE is on.
-        public static final int PRIORITY_LOW_POWER_MODE = 10;
-
-        // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
-        // higher priority voters' result is a range, it will fix the rate to a single choice.
-        // It's used to avoid refresh rate switches in certain conditions which may result in the
-        // user seeing the display flickering when the switches occur.
-        public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 11;
-
-        // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
-        public static final int PRIORITY_SKIN_TEMPERATURE = 12;
-
-        // The proximity sensor needs the refresh rate to be locked in order to function, so this is
-        // set to a high priority.
-        public static final int PRIORITY_PROXIMITY = 13;
-
-        // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
-        // to function, so this needs to be the highest priority of all votes.
-        public static final int PRIORITY_UDFPS = 14;
-
-        // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
-        // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
-
-        public static final int MIN_PRIORITY = PRIORITY_DEFAULT_RENDER_FRAME_RATE;
-        public static final int MAX_PRIORITY = PRIORITY_UDFPS;
-
-        // The cutoff for the app request refresh rate range. Votes with priorities lower than this
-        // value will not be considered when constructing the app request refresh rate range.
-        public static final int APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF =
-                PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE;
-
-        /**
-         * A value signifying an invalid width or height in a vote.
-         */
-        public static final int INVALID_SIZE = -1;
-
-        /**
-         * The requested width of the display in pixels, or INVALID_SIZE;
-         */
-        public final int width;
-        /**
-         * The requested height of the display in pixels, or INVALID_SIZE;
-         */
-        public final int height;
-        /**
-         * Information about the refresh rate frame rate ranges DM would like to set the display to.
-         */
-        public final RefreshRateRanges refreshRateRanges;
-
-        /**
-         * Whether refresh rate switching should be disabled (i.e. the refresh rate range is
-         * a single value).
-         */
-        public final boolean disableRefreshRateSwitching;
-
-        /**
-         * The preferred refresh rate selected by the app. It is used to validate that the summary
-         * refresh rate ranges include this value, and are not restricted by a lower priority vote.
-         */
-        public final float appRequestBaseModeRefreshRate;
-
-        public static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) {
-            return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate, 0,
-                    Float.POSITIVE_INFINITY,
-                    minRefreshRate == maxRefreshRate, 0f);
-        }
-
-        public static Vote forRenderFrameRates(float minFrameRate, float maxFrameRate) {
-            return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, minFrameRate,
-                    maxFrameRate,
-                    false, 0f);
-        }
-
-        public static Vote forSize(int width, int height) {
-            return new Vote(width, height, 0, Float.POSITIVE_INFINITY, 0, Float.POSITIVE_INFINITY,
-                    false,
-                    0f);
-        }
-
-        public static Vote forDisableRefreshRateSwitching() {
-            return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0,
-                    Float.POSITIVE_INFINITY, true,
-                    0f);
-        }
-
-        public static Vote forBaseModeRefreshRate(float baseModeRefreshRate) {
-            return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0,
-                    Float.POSITIVE_INFINITY, false,
-                    baseModeRefreshRate);
-        }
-
-        private Vote(int width, int height,
-                float minPhysicalRefreshRate,
-                float maxPhysicalRefreshRate,
-                float minRenderFrameRate,
-                float maxRenderFrameRate,
-                boolean disableRefreshRateSwitching,
-                float baseModeRefreshRate) {
-            this.width = width;
-            this.height = height;
-            this.refreshRateRanges = new RefreshRateRanges(
-                    new RefreshRateRange(minPhysicalRefreshRate, maxPhysicalRefreshRate),
-                    new RefreshRateRange(minRenderFrameRate, maxRenderFrameRate));
-            this.disableRefreshRateSwitching = disableRefreshRateSwitching;
-            this.appRequestBaseModeRefreshRate = baseModeRefreshRate;
-        }
-
-        public static String priorityToString(int priority) {
-            switch (priority) {
-                case PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE:
-                    return "PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE";
-                case PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE:
-                    return "PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE";
-                case PRIORITY_APP_REQUEST_SIZE:
-                    return "PRIORITY_APP_REQUEST_SIZE";
-                case PRIORITY_DEFAULT_RENDER_FRAME_RATE:
-                    return "PRIORITY_DEFAULT_REFRESH_RATE";
-                case PRIORITY_FLICKER_REFRESH_RATE:
-                    return "PRIORITY_FLICKER_REFRESH_RATE";
-                case PRIORITY_FLICKER_REFRESH_RATE_SWITCH:
-                    return "PRIORITY_FLICKER_REFRESH_RATE_SWITCH";
-                case PRIORITY_HIGH_BRIGHTNESS_MODE:
-                    return "PRIORITY_HIGH_BRIGHTNESS_MODE";
-                case PRIORITY_PROXIMITY:
-                    return "PRIORITY_PROXIMITY";
-                case PRIORITY_LOW_POWER_MODE:
-                    return "PRIORITY_LOW_POWER_MODE";
-                case PRIORITY_SKIN_TEMPERATURE:
-                    return "PRIORITY_SKIN_TEMPERATURE";
-                case PRIORITY_UDFPS:
-                    return "PRIORITY_UDFPS";
-                case PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE:
-                    return "PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE";
-                case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE:
-                    return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE";
-                case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE:
-                    return "PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE";
-                case PRIORITY_LAYOUT_LIMITED_FRAME_RATE:
-                    return "PRIORITY_LAYOUT_LIMITED_FRAME_RATE";
-                default:
-                    return Integer.toString(priority);
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "Vote{"
-                    + "width=" + width + ", height=" + height
-                    + ", refreshRateRanges=" + refreshRateRanges
-                    + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching
-                    + ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate + "}";
-        }
-    }
-
-    @VisibleForTesting
     final class SettingsObserver extends ContentObserver {
         private final Uri mSmoothDisplaySetting =
                 Settings.System.getUriFor(Settings.System.SMOOTH_DISPLAY);
@@ -1510,7 +1200,7 @@
             } else {
                 vote = null;
             }
-            updateVoteLocked(Vote.PRIORITY_LOW_POWER_MODE, vote);
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_LOW_POWER_MODE, vote);
             mBrightnessObserver.onLowPowerModeEnabledLocked(inLowPowerMode);
         }
 
@@ -1529,13 +1219,14 @@
             Vote peakVote = peakRefreshRate == 0f
                     ? null
                     : Vote.forRenderFrameRates(0f, Math.max(minRefreshRate, peakRefreshRate));
-            updateVoteLocked(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, peakVote);
-            updateVoteLocked(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+                    peakVote);
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
                     Vote.forRenderFrameRates(minRefreshRate, Float.POSITIVE_INFINITY));
             Vote defaultVote =
                     defaultRefreshRate == 0f
                             ? null : Vote.forRenderFrameRates(0f, defaultRefreshRate);
-            updateVoteLocked(Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE, defaultVote);
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE, defaultVote);
 
             float maxRefreshRate;
             if (peakRefreshRate == 0f && defaultRefreshRate == 0f) {
@@ -1619,9 +1310,9 @@
                 sizeVote = null;
             }
 
-            updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
                     baseModeRefreshRateVote);
-            updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
         }
 
         private void setAppPreferredRefreshRateRangeLocked(int displayId,
@@ -1652,11 +1343,8 @@
                 mAppPreferredRefreshRateRangeByDisplay.remove(displayId);
                 vote = null;
             }
-            synchronized (mLock) {
-                updateVoteLocked(displayId,
-                        Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
-                        vote);
-            }
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
+                    vote);
         }
 
         private Display.Mode findModeByIdLocked(int displayId, int modeId) {
@@ -1696,23 +1384,22 @@
         // calling into us already holding its own lock.
         private final Context mContext;
         private final Handler mHandler;
-        private final BallotBox mBallotBox;
+        private final VotesStorage mVotesStorage;
 
-        DisplayObserver(Context context, Handler handler, BallotBox ballotBox) {
+        DisplayObserver(Context context, Handler handler, VotesStorage votesStorage) {
             mContext = context;
             mHandler = handler;
-            mBallotBox = ballotBox;
+            mVotesStorage = votesStorage;
         }
 
         public void observe() {
-            DisplayManager dm = mContext.getSystemService(DisplayManager.class);
-            dm.registerDisplayListener(this, mHandler);
+            mInjector.registerDisplayListener(this, mHandler);
 
             // Populate existing displays
             SparseArray<Display.Mode[]> modes = new SparseArray<>();
             SparseArray<Display.Mode> defaultModes = new SparseArray<>();
             DisplayInfo info = new DisplayInfo();
-            Display[] displays = dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+            Display[] displays = mInjector.getDisplays();
             for (Display d : displays) {
                 final int displayId = d.getDisplayId();
                 d.getDisplayInfo(info);
@@ -1753,23 +1440,16 @@
 
         @Nullable
         private DisplayInfo getDisplayInfo(int displayId) {
-            Display d = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
-            if (d == null) {
-                // We can occasionally get a display added or changed event for a display that was
-                // subsequently removed, which means this returns null. Check this case and bail
-                // out early; if it gets re-attached we'll eventually get another call back for it.
-                return null;
-            }
             DisplayInfo info = new DisplayInfo();
-            d.getDisplayInfo(info);
-            return info;
+            // Display info might be invalid, in this case return null
+            return mInjector.getDisplayInfo(displayId, info) ? info : null;
         }
 
         private void updateLayoutLimitedFrameRate(int displayId, @Nullable DisplayInfo info) {
             Vote vote = info != null && info.layoutLimitedRefreshRate != null
                     ? Vote.forPhysicalRefreshRates(info.layoutLimitedRefreshRate.min,
                     info.layoutLimitedRefreshRate.max) : null;
-            mBallotBox.vote(displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, vote);
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, vote);
         }
 
         private void updateDisplayModes(int displayId, @Nullable DisplayInfo info) {
@@ -2091,8 +1771,8 @@
                 updateSensorStatus();
                 if (!changeable) {
                     // Revoke previous vote from BrightnessObserver
-                    updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE, null);
-                    updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, null);
+                    mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE, null);
+                    mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, null);
                 }
             }
         }
@@ -2417,8 +2097,9 @@
                 Slog.d(TAG, "Display brightness " + mBrightness + ", ambient lux " +  mAmbientLux
                         + ", Vote " + refreshRateVote);
             }
-            updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE, refreshRateVote);
-            updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, refreshRateSwitchingVote);
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE, refreshRateVote);
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH,
+                    refreshRateSwitchingVote);
         }
 
         private boolean hasValidLowZone() {
@@ -2432,8 +2113,7 @@
         }
 
         private void updateDefaultDisplayState() {
-            Display display = mContext.getSystemService(DisplayManager.class)
-                    .getDisplay(Display.DEFAULT_DISPLAY);
+            Display display = mInjector.getDisplay(Display.DEFAULT_DISPLAY);
             if (display == null) {
                 return;
             }
@@ -2687,7 +2367,7 @@
             } else {
                 vote = null;
             }
-            DisplayModeDirector.this.updateVoteLocked(displayId, votePriority, vote);
+            mVotesStorage.updateVote(displayId, votePriority, vote);
         }
 
         void dumpLocked(PrintWriter pw) {
@@ -2713,7 +2393,7 @@
         private final String mProximitySensorName = null;
         private final String mProximitySensorType = Sensor.STRING_TYPE_PROXIMITY;
 
-        private final BallotBox mBallotBox;
+        private final VotesStorage mVotesStorage;
         private final Context mContext;
         private final Injector mInjector;
         @GuardedBy("mSensorObserverLock")
@@ -2725,9 +2405,9 @@
         @GuardedBy("mSensorObserverLock")
         private boolean mIsProxActive = false;
 
-        SensorObserver(Context context, BallotBox ballotBox, Injector injector) {
+        SensorObserver(Context context, VotesStorage votesStorage, Injector injector) {
             mContext = context;
-            mBallotBox = ballotBox;
+            mVotesStorage = votesStorage;
             mInjector = injector;
         }
 
@@ -2750,8 +2430,7 @@
             sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this);
 
             synchronized (mSensorObserverLock) {
-                for (Display d : mDisplayManager.getDisplays(
-                        DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
+                for (Display d : mInjector.getDisplays()) {
                     mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d));
                 }
             }
@@ -2762,8 +2441,7 @@
         }
 
         private void recalculateVotesLocked() {
-            final Display[] displays = mDisplayManager.getDisplays(
-                    DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+            final Display[] displays = mInjector.getDisplays();
             for (Display d : displays) {
                 int displayId = d.getDisplayId();
                 Vote vote = null;
@@ -2775,7 +2453,7 @@
                         vote = Vote.forPhysicalRefreshRates(rate.min, rate.max);
                     }
                 }
-                mBallotBox.vote(displayId, Vote.PRIORITY_PROXIMITY, vote);
+                mVotesStorage.updateVote(displayId, Vote.PRIORITY_PROXIMITY, vote);
             }
         }
 
@@ -2794,7 +2472,7 @@
 
         @Override
         public void onDisplayAdded(int displayId) {
-            boolean isDozeState = mInjector.isDozeState(mDisplayManager.getDisplay(displayId));
+            boolean isDozeState = mInjector.isDozeState(mInjector.getDisplay(displayId));
             synchronized (mSensorObserverLock) {
                 mDozeStateByDisplay.put(displayId, isDozeState);
                 recalculateVotesLocked();
@@ -2806,7 +2484,7 @@
             boolean wasDozeState = mDozeStateByDisplay.get(displayId);
             synchronized (mSensorObserverLock) {
                 mDozeStateByDisplay.put(displayId,
-                        mInjector.isDozeState(mDisplayManager.getDisplay(displayId)));
+                        mInjector.isDozeState(mInjector.getDisplay(displayId)));
                 if (wasDozeState != mDozeStateByDisplay.get(displayId)) {
                     recalculateVotesLocked();
                 }
@@ -2828,7 +2506,7 @@
      * DisplayManagerInternal but originate in the display-device-config file.
      */
     public class HbmObserver implements DisplayManager.DisplayListener {
-        private final BallotBox mBallotBox;
+        private final VotesStorage mVotesStorage;
         private final Handler mHandler;
         private final SparseIntArray mHbmMode = new SparseIntArray();
         private final SparseBooleanArray mHbmActive = new SparseBooleanArray();
@@ -2839,10 +2517,10 @@
 
         private DisplayManagerInternal mDisplayManagerInternal;
 
-        HbmObserver(Injector injector, BallotBox ballotBox, Handler handler,
+        HbmObserver(Injector injector, VotesStorage votesStorage, Handler handler,
                 DeviceConfigDisplaySettings displaySettings) {
             mInjector = injector;
-            mBallotBox = ballotBox;
+            mVotesStorage = votesStorage;
             mHandler = handler;
             mDeviceConfigDisplaySettings = displaySettings;
         }
@@ -2912,7 +2590,7 @@
 
         @Override
         public void onDisplayRemoved(int displayId) {
-            mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, null);
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, null);
             mHbmMode.delete(displayId);
             mHbmActive.delete(displayId);
         }
@@ -2979,7 +2657,7 @@
                 }
 
             }
-            mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote);
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote);
         }
 
         void dumpLocked(PrintWriter pw) {
@@ -3176,8 +2854,13 @@
                 @NonNull ContentObserver observer);
 
         void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
+                Handler handler);
+
+        void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
                 Handler handler, long flags);
 
+        Display getDisplay(int displayId);
+
         Display[] getDisplays();
 
         boolean getDisplayInfo(int displayId, DisplayInfo displayInfo);
@@ -3222,11 +2905,22 @@
 
         @Override
         public void registerDisplayListener(DisplayManager.DisplayListener listener,
+                Handler handler) {
+            getDisplayManager().registerDisplayListener(listener, handler);
+        }
+
+        @Override
+        public void registerDisplayListener(DisplayManager.DisplayListener listener,
                 Handler handler, long flags) {
             getDisplayManager().registerDisplayListener(listener, handler, flags);
         }
 
         @Override
+        public Display getDisplay(int displayId) {
+            return getDisplayManager().getDisplay(displayId);
+        }
+
+        @Override
         public Display[] getDisplays() {
             return getDisplayManager().getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
         }
@@ -3234,10 +2928,13 @@
         @Override
         public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) {
             Display display = getDisplayManager().getDisplay(displayId);
-            if (display != null) {
-                return display.getDisplayInfo(displayInfo);
+            if (display == null) {
+                // We can occasionally get a display added or changed event for a display that was
+                // subsequently removed, which means this returns null. Check this case and bail
+                // out early; if it gets re-attached we'll eventually get another call back for it.
+                return false;
             }
-            return false;
+            return display.getDisplayInfo(displayInfo);
         }
 
         @Override
@@ -3291,8 +2988,4 @@
                     ServiceManager.getService(Context.THERMAL_SERVICE));
         }
     }
-
-    interface BallotBox {
-        void vote(int displayId, int priority, Vote vote);
-    }
 }
diff --git a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
index c04735d..58e1550 100644
--- a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
+++ b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
@@ -37,7 +37,7 @@
         DisplayManager.DisplayListener {
     private static final String TAG = "SkinThermalStatusObserver";
 
-    private final DisplayModeDirector.BallotBox mBallotBox;
+    private final VotesStorage mVotesStorage;
     private final DisplayModeDirector.Injector mInjector;
 
     private boolean mLoggingEnabled;
@@ -52,15 +52,15 @@
             mThermalThrottlingByDisplay = new SparseArray<>();
 
     SkinThermalStatusObserver(DisplayModeDirector.Injector injector,
-            DisplayModeDirector.BallotBox ballotBox) {
-        this(injector, ballotBox, BackgroundThread.getHandler());
+            VotesStorage votesStorage) {
+        this(injector, votesStorage, BackgroundThread.getHandler());
     }
 
     @VisibleForTesting
     SkinThermalStatusObserver(DisplayModeDirector.Injector injector,
-            DisplayModeDirector.BallotBox ballotBox, Handler handler) {
+            VotesStorage votesStorage, Handler handler) {
         mInjector = injector;
-        mBallotBox = ballotBox;
+        mVotesStorage = votesStorage;
         mHandler = handler;
     }
 
@@ -112,8 +112,8 @@
     public void onDisplayRemoved(int displayId) {
         synchronized (mThermalObserverLock) {
             mThermalThrottlingByDisplay.remove(displayId);
-            mHandler.post(() -> mBallotBox.vote(displayId,
-                    DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE, null));
+            mHandler.post(() -> mVotesStorage.updateVote(displayId,
+                    Vote.PRIORITY_SKIN_TEMPERATURE, null));
         }
         if (mLoggingEnabled) {
             Slog.d(TAG, "Display removed and voted: displayId=" + displayId);
@@ -138,7 +138,7 @@
         for (Display d : displays) {
             final int displayId = d.getDisplayId();
             d.getDisplayInfo(info);
-            localMap.put(displayId, info.refreshRateThermalThrottling);
+            localMap.put(displayId, info.thermalRefreshRateThrottling);
         }
         synchronized (mThermalObserverLock) {
             for (int i = 0; i < size; i++) {
@@ -154,7 +154,7 @@
         DisplayInfo displayInfo = new DisplayInfo();
         mInjector.getDisplayInfo(displayId, displayInfo);
         SparseArray<SurfaceControl.RefreshRateRange> throttlingMap =
-                displayInfo.refreshRateThermalThrottling;
+                displayInfo.thermalRefreshRateThrottling;
 
         synchronized (mThermalObserverLock) {
             mThermalThrottlingByDisplay.put(displayId, throttlingMap);
@@ -218,11 +218,11 @@
         SurfaceControl.RefreshRateRange foundRange = findBestMatchingRefreshRateRange(currentStatus,
                 throttlingMap);
         // if status <= currentStatus not found in the map reset vote
-        DisplayModeDirector.Vote vote = null;
+        Vote vote = null;
         if (foundRange != null) { // otherwise vote with found range
-            vote = DisplayModeDirector.Vote.forRenderFrameRates(foundRange.min, foundRange.max);
+            vote = Vote.forRenderFrameRates(foundRange.min, foundRange.max);
         }
-        mBallotBox.vote(displayId, DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE, vote);
+        mVotesStorage.updateVote(displayId, Vote.PRIORITY_SKIN_TEMPERATURE, vote);
         if (mLoggingEnabled) {
             Slog.d(TAG, "Voted: vote=" + vote + ", display =" + displayId);
         }
@@ -244,11 +244,11 @@
 
     private void fallbackReportThrottlingIfNeeded(int displayId,
             @Temperature.ThrottlingStatus int currentStatus) {
-        DisplayModeDirector.Vote vote = null;
+        Vote vote = null;
         if (currentStatus >= Temperature.THROTTLING_CRITICAL) {
-            vote = DisplayModeDirector.Vote.forRenderFrameRates(0f, 60f);
+            vote = Vote.forRenderFrameRates(0f, 60f);
         }
-        mBallotBox.vote(displayId, DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE, vote);
+        mVotesStorage.updateVote(displayId, Vote.PRIORITY_SKIN_TEMPERATURE, vote);
         if (mLoggingEnabled) {
             Slog.d(TAG, "Voted(fallback): vote=" + vote + ", display =" + displayId);
         }
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
new file mode 100644
index 0000000..a42d8f2
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode;
+
+import android.view.SurfaceControl;
+
+final class Vote {
+    // DEFAULT_RENDER_FRAME_RATE votes for render frame rate [0, DEFAULT]. As the lowest
+    // priority vote, it's overridden by all other considerations. It acts to set a default
+    // frame rate for a device.
+    static final int PRIORITY_DEFAULT_RENDER_FRAME_RATE = 0;
+
+    // PRIORITY_FLICKER_REFRESH_RATE votes for a single refresh rate like [60,60], [90,90] or
+    // null. It is used to set a preferred refresh rate value in case the higher priority votes
+    // result is a range.
+    static final int PRIORITY_FLICKER_REFRESH_RATE = 1;
+
+    // High-brightness-mode may need a specific range of refresh-rates to function properly.
+    static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 2;
+
+    // SETTING_MIN_RENDER_FRAME_RATE is used to propose a lower bound of the render frame rate.
+    // It votes [minRefreshRate, Float.POSITIVE_INFINITY]
+    static final int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3;
+
+    // APP_REQUEST_RENDER_FRAME_RATE_RANGE is used to for internal apps to limit the render
+    // frame rate in certain cases, mostly to preserve power.
+    // @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate
+    // @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate
+    // It votes to [preferredMinRefreshRate, preferredMaxRefreshRate].
+    static final int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 4;
+
+    // We split the app request into different priorities in case we can satisfy one desire
+    // without the other.
+
+    // Application can specify preferred refresh rate with below attrs.
+    // @see android.view.WindowManager.LayoutParams#preferredRefreshRate
+    // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId
+    //
+    // When the app specifies a LayoutParams#preferredDisplayModeId, in addition to the
+    // refresh rate, it also chooses a preferred size (resolution) as part of the selected
+    // mode id. The app preference is then translated to APP_REQUEST_BASE_MODE_REFRESH_RATE and
+    // optionally to APP_REQUEST_SIZE as well, if a mode id was selected.
+    // The system also forces some apps like denylisted app to run at a lower refresh rate.
+    // @see android.R.array#config_highRefreshRateBlacklist
+    //
+    // When summarizing the votes and filtering the allowed display modes, these votes determine
+    // which mode id should be the base mode id to be sent to SurfaceFlinger:
+    // - APP_REQUEST_BASE_MODE_REFRESH_RATE is used to validate the vote summary. If a summary
+    //   includes a base mode refresh rate, but it is not in the refresh rate range, then the
+    //   summary is considered invalid so we could drop a lower priority vote and try again.
+    // - APP_REQUEST_SIZE is used to filter out display modes of a different size.
+    //
+    // The preferred refresh rate is set on the main surface of the app outside of
+    // DisplayModeDirector.
+    // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded
+    static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 5;
+    static final int PRIORITY_APP_REQUEST_SIZE = 6;
+
+    // SETTING_PEAK_RENDER_FRAME_RATE has a high priority and will restrict the bounds of the
+    // rest of low priority voters. It votes [0, max(PEAK, MIN)]
+    static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 7;
+
+    // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh
+    // rate to max value (same as for PRIORITY_UDFPS) on lock screen
+    static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 8;
+
+    // For concurrent displays we want to limit refresh rate on all displays
+    static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 9;
+
+    // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if
+    // Settings.Global.LOW_POWER_MODE is on.
+    static final int PRIORITY_LOW_POWER_MODE = 10;
+
+    // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
+    // higher priority voters' result is a range, it will fix the rate to a single choice.
+    // It's used to avoid refresh rate switches in certain conditions which may result in the
+    // user seeing the display flickering when the switches occur.
+    static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 11;
+
+    // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
+    static final int PRIORITY_SKIN_TEMPERATURE = 12;
+
+    // The proximity sensor needs the refresh rate to be locked in order to function, so this is
+    // set to a high priority.
+    static final int PRIORITY_PROXIMITY = 13;
+
+    // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
+    // to function, so this needs to be the highest priority of all votes.
+    static final int PRIORITY_UDFPS = 14;
+
+    // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
+    // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
+
+    static final int MIN_PRIORITY = PRIORITY_DEFAULT_RENDER_FRAME_RATE;
+    static final int MAX_PRIORITY = PRIORITY_UDFPS;
+
+    // The cutoff for the app request refresh rate range. Votes with priorities lower than this
+    // value will not be considered when constructing the app request refresh rate range.
+    static final int APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF =
+            PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE;
+
+    /**
+     * A value signifying an invalid width or height in a vote.
+     */
+    static final int INVALID_SIZE = -1;
+
+    /**
+     * The requested width of the display in pixels, or INVALID_SIZE;
+     */
+    public final int width;
+    /**
+     * The requested height of the display in pixels, or INVALID_SIZE;
+     */
+    public final int height;
+    /**
+     * Information about the refresh rate frame rate ranges DM would like to set the display to.
+     */
+    public final SurfaceControl.RefreshRateRanges refreshRateRanges;
+
+    /**
+     * Whether refresh rate switching should be disabled (i.e. the refresh rate range is
+     * a single value).
+     */
+    public final boolean disableRefreshRateSwitching;
+
+    /**
+     * The preferred refresh rate selected by the app. It is used to validate that the summary
+     * refresh rate ranges include this value, and are not restricted by a lower priority vote.
+     */
+    public final float appRequestBaseModeRefreshRate;
+
+    static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) {
+        return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate, 0,
+                Float.POSITIVE_INFINITY,
+                minRefreshRate == maxRefreshRate, 0f);
+    }
+
+    static Vote forRenderFrameRates(float minFrameRate, float maxFrameRate) {
+        return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, minFrameRate,
+                maxFrameRate,
+                false, 0f);
+    }
+
+    static Vote forSize(int width, int height) {
+        return new Vote(width, height, 0, Float.POSITIVE_INFINITY, 0, Float.POSITIVE_INFINITY,
+                false,
+                0f);
+    }
+
+    static Vote forDisableRefreshRateSwitching() {
+        return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0,
+                Float.POSITIVE_INFINITY, true,
+                0f);
+    }
+
+    static Vote forBaseModeRefreshRate(float baseModeRefreshRate) {
+        return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0,
+                Float.POSITIVE_INFINITY, false,
+                baseModeRefreshRate);
+    }
+
+    private Vote(int width, int height,
+            float minPhysicalRefreshRate,
+            float maxPhysicalRefreshRate,
+            float minRenderFrameRate,
+            float maxRenderFrameRate,
+            boolean disableRefreshRateSwitching,
+            float baseModeRefreshRate) {
+        this.width = width;
+        this.height = height;
+        this.refreshRateRanges = new SurfaceControl.RefreshRateRanges(
+                new SurfaceControl.RefreshRateRange(minPhysicalRefreshRate, maxPhysicalRefreshRate),
+                new SurfaceControl.RefreshRateRange(minRenderFrameRate, maxRenderFrameRate));
+        this.disableRefreshRateSwitching = disableRefreshRateSwitching;
+        this.appRequestBaseModeRefreshRate = baseModeRefreshRate;
+    }
+
+    static String priorityToString(int priority) {
+        switch (priority) {
+            case PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE:
+                return "PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE";
+            case PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE:
+                return "PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE";
+            case PRIORITY_APP_REQUEST_SIZE:
+                return "PRIORITY_APP_REQUEST_SIZE";
+            case PRIORITY_DEFAULT_RENDER_FRAME_RATE:
+                return "PRIORITY_DEFAULT_REFRESH_RATE";
+            case PRIORITY_FLICKER_REFRESH_RATE:
+                return "PRIORITY_FLICKER_REFRESH_RATE";
+            case PRIORITY_FLICKER_REFRESH_RATE_SWITCH:
+                return "PRIORITY_FLICKER_REFRESH_RATE_SWITCH";
+            case PRIORITY_HIGH_BRIGHTNESS_MODE:
+                return "PRIORITY_HIGH_BRIGHTNESS_MODE";
+            case PRIORITY_PROXIMITY:
+                return "PRIORITY_PROXIMITY";
+            case PRIORITY_LOW_POWER_MODE:
+                return "PRIORITY_LOW_POWER_MODE";
+            case PRIORITY_SKIN_TEMPERATURE:
+                return "PRIORITY_SKIN_TEMPERATURE";
+            case PRIORITY_UDFPS:
+                return "PRIORITY_UDFPS";
+            case PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE:
+                return "PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE";
+            case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE:
+                return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE";
+            case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE:
+                return "PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE";
+            case PRIORITY_LAYOUT_LIMITED_FRAME_RATE:
+                return "PRIORITY_LAYOUT_LIMITED_FRAME_RATE";
+            default:
+                return Integer.toString(priority);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Vote: {"
+                + "width: " + width + ", height: " + height
+                + ", refreshRateRanges: " + refreshRateRanges
+                + ", disableRefreshRateSwitching: " + disableRefreshRateSwitching
+                + ", appRequestBaseModeRefreshRate: "  + appRequestBaseModeRefreshRate + "}";
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java
new file mode 100644
index 0000000..dadcebe
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/VotesStorage.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+
+class VotesStorage {
+    private static final String TAG = "VotesStorage";
+    // Special ID used to indicate that given vote is to be applied globally, rather than to a
+    // specific display.
+    private static final int GLOBAL_ID = -1;
+
+    private boolean mLoggingEnabled;
+
+    private final Listener mListener;
+
+    private final Object mStorageLock = new Object();
+    // A map from the display ID to the collection of votes and their priority. The latter takes
+    // the form of another map from the priority to the vote itself so that each priority is
+    // guaranteed to have exactly one vote, which is also easily and efficiently replaceable.
+    @GuardedBy("mStorageLock")
+    private final SparseArray<SparseArray<Vote>> mVotesByDisplay = new SparseArray<>();
+
+    VotesStorage(@NonNull Listener listener) {
+        mListener = listener;
+    }
+    /** sets logging enabled/disabled for this class */
+    void setLoggingEnabled(boolean loggingEnabled) {
+        mLoggingEnabled = loggingEnabled;
+    }
+    /**
+     * gets all votes for specific display, note that global display votes are also added to result
+     */
+    @NonNull
+    SparseArray<Vote> getVotes(int displayId) {
+        SparseArray<Vote> votesLocal;
+        SparseArray<Vote> globalVotesLocal;
+        synchronized (mStorageLock) {
+            SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
+            votesLocal = displayVotes != null ? displayVotes.clone() : new SparseArray<>();
+            SparseArray<Vote> globalVotes = mVotesByDisplay.get(GLOBAL_ID);
+            globalVotesLocal = globalVotes != null ? globalVotes.clone() : new SparseArray<>();
+        }
+        for (int i = 0; i < globalVotesLocal.size(); i++) {
+            int priority = globalVotesLocal.keyAt(i);
+            if (!votesLocal.contains(priority)) {
+                votesLocal.put(priority, globalVotesLocal.valueAt(i));
+            }
+        }
+        return votesLocal;
+    }
+
+    /** updates vote storage for all displays */
+    void updateGlobalVote(int priority, @Nullable Vote vote) {
+        updateVote(GLOBAL_ID, priority, vote);
+    }
+
+    /** updates vote storage */
+    void updateVote(int displayId, int priority, @Nullable Vote vote) {
+        if (mLoggingEnabled) {
+            Slog.i(TAG, "updateVoteLocked(displayId=" + displayId
+                    + ", priority=" + Vote.priorityToString(priority)
+                    + ", vote=" + vote + ")");
+        }
+        if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) {
+            Slog.w(TAG, "Received a vote with an invalid priority, ignoring:"
+                    + " priority=" + Vote.priorityToString(priority)
+                    + ", vote=" + vote);
+            return;
+        }
+        SparseArray<Vote> votes;
+        synchronized (mStorageLock) {
+            if (mVotesByDisplay.contains(displayId)) {
+                votes = mVotesByDisplay.get(displayId);
+            } else {
+                votes = new SparseArray<>();
+                mVotesByDisplay.put(displayId, votes);
+            }
+            if (vote != null) {
+                votes.put(priority, vote);
+            } else {
+                votes.remove(priority);
+            }
+        }
+        if (mLoggingEnabled) {
+            Slog.i(TAG, "Updated votes for display=" + displayId + " votes=" + votes);
+        }
+        mListener.onChanged();
+    }
+
+    /** dump class values, for debugging */
+    void dump(@NonNull PrintWriter pw) {
+        SparseArray<SparseArray<Vote>> votesByDisplayLocal = new SparseArray<>();
+        synchronized (mStorageLock) {
+            for (int i = 0; i < mVotesByDisplay.size(); i++) {
+                votesByDisplayLocal.put(mVotesByDisplay.keyAt(i),
+                        mVotesByDisplay.valueAt(i).clone());
+            }
+        }
+        pw.println("  mVotesByDisplay:");
+        for (int i = 0; i < votesByDisplayLocal.size(); i++) {
+            SparseArray<Vote> votes = votesByDisplayLocal.valueAt(i);
+            if (votes.size() == 0) {
+                continue;
+            }
+            pw.println("    " + votesByDisplayLocal.keyAt(i) + ":");
+            for (int p = Vote.MAX_PRIORITY; p >= Vote.MIN_PRIORITY; p--) {
+                Vote vote = votes.get(p);
+                if (vote == null) {
+                    continue;
+                }
+                pw.println("      " + Vote.priorityToString(p) + " -> " + vote);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void injectVotesByDisplay(SparseArray<SparseArray<Vote>> votesByDisplay) {
+        synchronized (mStorageLock) {
+            mVotesByDisplay.clear();
+            for (int i = 0; i < votesByDisplay.size(); i++) {
+                mVotesByDisplay.put(votesByDisplay.keyAt(i), votesByDisplay.valueAt(i));
+            }
+        }
+    }
+
+    interface Listener {
+        void onChanged();
+    }
+}
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index 048308e..48c346a 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -199,8 +199,11 @@
         }
         if (brightness.isPresent()) {
             int brightnessValue = Math.max(0, Math.min(MAX_BRIGHTNESS, brightness.getAsInt()));
-            int brightnessLevel = Arrays.binarySearch(BRIGHTNESS_VALUE_FOR_LEVEL, brightnessValue);
-            updateBacklightState(inputDevice.getId(), keyboardBacklight, brightnessLevel,
+            int index = Arrays.binarySearch(BRIGHTNESS_VALUE_FOR_LEVEL, brightnessValue);
+            if (index < 0) {
+                index = Math.min(NUM_BRIGHTNESS_CHANGE_STEPS, -(index + 1));
+            }
+            updateBacklightState(inputDevice.getId(), keyboardBacklight, index,
                     false /* isTriggeredByKeyPress */);
             if (DEBUG) {
                 Slog.d(TAG, "Restoring brightness level " + brightness.getAsInt());
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 72c7dad..d8716b3 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -16,6 +16,8 @@
 
 package com.android.server.input;
 
+import android.annotation.AnyThread;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -99,6 +101,7 @@
     private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2;
     private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3;
     private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
+    private static final int MSG_CURRENT_IME_INFO_CHANGED = 5;
 
     private final Context mContext;
     private final NativeInputManagerService mNative;
@@ -108,16 +111,17 @@
     private final Handler mHandler;
 
     // Connected keyboards with associated keyboard layouts (either auto-detected or manually
-    // selected layout). If the mapped value is null/empty, it means that no layout has been
-    // configured for the keyboard and user might need to manually configure it from the Settings.
-    private final SparseArray<Set<String>> mConfiguredKeyboards = new SparseArray<>();
+    // selected layout).
+    private final SparseArray<KeyboardConfiguration> mConfiguredKeyboards = new SparseArray<>();
     private Toast mSwitchedKeyboardLayoutToast;
 
     // This cache stores "best-matched" layouts so that we don't need to run the matching
     // algorithm repeatedly.
     @GuardedBy("mKeyboardLayoutCache")
     private final Map<String, String> mKeyboardLayoutCache = new ArrayMap<>();
+    private final Object mImeInfoLock = new Object();
     @Nullable
+    @GuardedBy("mImeInfoLock")
     private ImeInfo mCurrentImeInfo;
 
     KeyboardLayoutManager(Context context, NativeInputManagerService nativeService,
@@ -155,26 +159,32 @@
     }
 
     @Override
+    @MainThread
     public void onInputDeviceAdded(int deviceId) {
         onInputDeviceChanged(deviceId);
-        if (useNewSettingsUi()) {
-            // Force native callback to set up keyboard layout overlay for newly added keyboards
-            reloadKeyboardLayouts();
-        }
     }
 
     @Override
+    @MainThread
     public void onInputDeviceRemoved(int deviceId) {
         mConfiguredKeyboards.remove(deviceId);
         maybeUpdateNotification();
     }
 
     @Override
+    @MainThread
     public void onInputDeviceChanged(int deviceId) {
         final InputDevice inputDevice = getInputDevice(deviceId);
         if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
             return;
         }
+        KeyboardConfiguration config = mConfiguredKeyboards.get(deviceId);
+        if (config == null) {
+            config = new KeyboardConfiguration();
+            mConfiguredKeyboards.put(deviceId, config);
+        }
+
+        boolean needToShowNotification = false;
         if (!useNewSettingsUi()) {
             synchronized (mDataStore) {
                 String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
@@ -182,54 +192,66 @@
                     layout = getDefaultKeyboardLayout(inputDevice);
                     if (layout != null) {
                         setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
-                    } else {
-                        mConfiguredKeyboards.put(inputDevice.getId(), new HashSet<>());
                     }
                 }
+                config.setCurrentLayout(layout);
+                if (layout == null) {
+                    // In old settings show notification always until user manually selects a
+                    // layout in the settings.
+                    needToShowNotification = true;
+                }
             }
         } else {
             final InputDeviceIdentifier identifier = inputDevice.getIdentifier();
             final String key = getLayoutDescriptor(identifier);
             Set<String> selectedLayouts = new HashSet<>();
-            boolean needToShowMissingLayoutNotification = false;
             for (ImeInfo imeInfo : getImeInfoListForLayoutMapping()) {
                 // Check if the layout has been previously configured
                 String layout = getKeyboardLayoutForInputDeviceInternal(identifier,
                         new ImeInfo(imeInfo.mUserId, imeInfo.mImeSubtypeHandle,
                                 imeInfo.mImeSubtype));
                 if (layout == null) {
-                    needToShowMissingLayoutNotification = true;
-                    continue;
+                    // If even one layout not configured properly, we need to ask user to configure
+                    // the keyboard properly from the Settings.
+                    selectedLayouts.clear();
+                    break;
                 }
                 selectedLayouts.add(layout);
             }
 
-            if (needToShowMissingLayoutNotification) {
-                // If even one layout not configured properly we will show configuration
-                // notification allowing user to set the keyboard layout.
-                selectedLayouts.clear();
-            }
-
             if (DEBUG) {
                 Slog.d(TAG,
                         "Layouts selected for input device: " + identifier + " -> selectedLayouts: "
                                 + selectedLayouts);
             }
-            mConfiguredKeyboards.set(inputDevice.getId(), selectedLayouts);
+
+            config.setConfiguredLayouts(selectedLayouts);
+
+            // Update current layout: If there is a change then need to reload.
+            synchronized (mImeInfoLock) {
+                String layout = getKeyboardLayoutForInputDeviceInternal(
+                        inputDevice.getIdentifier(), mCurrentImeInfo);
+                if (!Objects.equals(layout, config.getCurrentLayout())) {
+                    config.setCurrentLayout(layout);
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            }
 
             synchronized (mDataStore) {
                 try {
-                    if (!mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
-                        // No need to show the notification only if layout selection didn't change
+                    if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
+                        // Need to show the notification only if layout selection changed
                         // from the previous configuration
-                        return;
+                        needToShowNotification = true;
                     }
                 } finally {
                     mDataStore.saveIfNeeded();
                 }
             }
         }
-        maybeUpdateNotification();
+        if (needToShowNotification) {
+            maybeUpdateNotification();
+        }
     }
 
     private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
@@ -323,12 +345,14 @@
         reloadKeyboardLayouts();
     }
 
+    @AnyThread
     public KeyboardLayout[] getKeyboardLayouts() {
         final ArrayList<KeyboardLayout> list = new ArrayList<>();
         visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> list.add(layout));
         return list.toArray(new KeyboardLayout[0]);
     }
 
+    @AnyThread
     public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
             final InputDeviceIdentifier identifier) {
         if (useNewSettingsUi()) {
@@ -375,6 +399,7 @@
                 KeyboardLayout[]::new);
     }
 
+    @AnyThread
     @Nullable
     public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
         Objects.requireNonNull(keyboardLayoutDescriptor,
@@ -543,6 +568,7 @@
         return key.toString();
     }
 
+    @AnyThread
     @Nullable
     public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
         if (useNewSettingsUi()) {
@@ -566,6 +592,7 @@
         }
     }
 
+    @AnyThread
     public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
         if (useNewSettingsUi()) {
@@ -592,6 +619,7 @@
         }
     }
 
+    @AnyThread
     public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
         if (useNewSettingsUi()) {
             Slog.e(TAG, "getEnabledKeyboardLayoutsForInputDevice API not supported");
@@ -608,6 +636,7 @@
         }
     }
 
+    @AnyThread
     public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
         if (useNewSettingsUi()) {
@@ -635,6 +664,7 @@
         }
     }
 
+    @AnyThread
     public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
         if (useNewSettingsUi()) {
@@ -667,6 +697,7 @@
         }
     }
 
+    @AnyThread
     public void switchKeyboardLayout(int deviceId, int direction) {
         if (useNewSettingsUi()) {
             Slog.e(TAG, "switchKeyboardLayout API not supported");
@@ -675,7 +706,7 @@
         mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
     }
 
-    // Must be called on handler.
+    @MainThread
     private void handleSwitchKeyboardLayout(int deviceId, int direction) {
         final InputDevice device = getInputDevice(deviceId);
         if (device != null) {
@@ -713,23 +744,14 @@
     }
 
     @Nullable
+    @AnyThread
     public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
         String keyboardLayoutDescriptor;
         if (useNewSettingsUi()) {
-            InputDevice inputDevice = getInputDevice(identifier);
-            if (inputDevice == null) {
-                // getKeyboardLayoutOverlay() called before input device added completely. Need
-                // to wait till the device is added which will call reloadKeyboardLayouts()
-                return null;
+            synchronized (mImeInfoLock) {
+                keyboardLayoutDescriptor = getKeyboardLayoutForInputDeviceInternal(identifier,
+                        mCurrentImeInfo);
             }
-            if (mCurrentImeInfo == null) {
-                // Haven't received onInputMethodSubtypeChanged() callback from IMMS. Will reload
-                // keyboard layouts once we receive the callback.
-                return null;
-            }
-
-            keyboardLayoutDescriptor = getKeyboardLayoutForInputDeviceInternal(identifier,
-                    mCurrentImeInfo);
         } else {
             keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
         }
@@ -755,6 +777,7 @@
         return result;
     }
 
+    @AnyThread
     @Nullable
     public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
@@ -773,6 +796,7 @@
         return layout;
     }
 
+    @AnyThread
     public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
             @Nullable InputMethodSubtype imeSubtype,
@@ -783,8 +807,8 @@
         }
         Objects.requireNonNull(keyboardLayoutDescriptor,
                 "keyboardLayoutDescriptor must not be null");
-        String key = createLayoutKey(identifier, userId,
-                InputMethodSubtypeHandle.of(imeInfo, imeSubtype));
+        String key = createLayoutKey(identifier,
+                new ImeInfo(userId, InputMethodSubtypeHandle.of(imeInfo, imeSubtype), imeSubtype));
         synchronized (mDataStore) {
             try {
                 // Key for storing into data store = <device descriptor>,<userId>,<subtypeHandle>
@@ -803,6 +827,7 @@
         }
     }
 
+    @AnyThread
     public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
             @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
             @Nullable InputMethodSubtype imeSubtype) {
@@ -815,8 +840,8 @@
     }
 
     private KeyboardLayout[] getKeyboardLayoutListForInputDeviceInternal(
-            InputDeviceIdentifier identifier, ImeInfo imeInfo) {
-        String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle);
+            InputDeviceIdentifier identifier, @Nullable ImeInfo imeInfo) {
+        String key = createLayoutKey(identifier, imeInfo);
 
         // Fetch user selected layout and always include it in layout list.
         String userSelectedLayout;
@@ -826,7 +851,7 @@
 
         final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>();
         String imeLanguageTag;
-        if (imeInfo.mImeSubtype == null) {
+        if (imeInfo == null || imeInfo.mImeSubtype == null) {
             imeLanguageTag = "";
         } else {
             ULocale imeLocale = imeInfo.mImeSubtype.getPhysicalKeyboardHintLanguageTag();
@@ -866,6 +891,7 @@
         return potentialLayouts.toArray(new KeyboardLayout[0]);
     }
 
+    @AnyThread
     public void onInputMethodSubtypeChanged(@UserIdInt int userId,
             @Nullable InputMethodSubtypeHandle subtypeHandle,
             @Nullable InputMethodSubtype subtype) {
@@ -879,25 +905,45 @@
             }
             return;
         }
-        if (mCurrentImeInfo == null || !subtypeHandle.equals(mCurrentImeInfo.mImeSubtypeHandle)
-                || mCurrentImeInfo.mUserId != userId) {
-            mCurrentImeInfo = new ImeInfo(userId, subtypeHandle, subtype);
-            mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
-            if (DEBUG) {
-                Slog.d(TAG, "InputMethodSubtype changed: userId=" + userId
-                        + " subtypeHandle=" + subtypeHandle);
+        synchronized (mImeInfoLock) {
+            if (mCurrentImeInfo == null || !subtypeHandle.equals(mCurrentImeInfo.mImeSubtypeHandle)
+                    || mCurrentImeInfo.mUserId != userId) {
+                mCurrentImeInfo = new ImeInfo(userId, subtypeHandle, subtype);
+                mHandler.sendEmptyMessage(MSG_CURRENT_IME_INFO_CHANGED);
+                if (DEBUG) {
+                    Slog.d(TAG, "InputMethodSubtype changed: userId=" + userId
+                            + " subtypeHandle=" + subtypeHandle);
+                }
+            }
+        }
+    }
+
+    @MainThread
+    private void onCurrentImeInfoChanged() {
+        synchronized (mImeInfoLock) {
+            for (int i = 0; i < mConfiguredKeyboards.size(); i++) {
+                InputDevice inputDevice = Objects.requireNonNull(
+                        getInputDevice(mConfiguredKeyboards.keyAt(i)));
+                String layout = getKeyboardLayoutForInputDeviceInternal(inputDevice.getIdentifier(),
+                        mCurrentImeInfo);
+                KeyboardConfiguration config = mConfiguredKeyboards.valueAt(i);
+                if (!Objects.equals(layout, config.getCurrentLayout())) {
+                    config.setCurrentLayout(layout);
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                    return;
+                }
             }
         }
     }
 
     @Nullable
     private String getKeyboardLayoutForInputDeviceInternal(InputDeviceIdentifier identifier,
-            ImeInfo imeInfo) {
+            @Nullable ImeInfo imeInfo) {
         InputDevice inputDevice = getInputDevice(identifier);
         if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
             return null;
         }
-        String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle);
+        String key = createLayoutKey(identifier, imeInfo);
         String layout;
         synchronized (mDataStore) {
             layout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key);
@@ -923,11 +969,7 @@
 
     @Nullable
     private static String getDefaultKeyboardLayoutBasedOnImeInfo(InputDevice inputDevice,
-            ImeInfo imeInfo, KeyboardLayout[] layoutList) {
-        if (imeInfo.mImeSubtypeHandle == null) {
-            return null;
-        }
-
+            @Nullable ImeInfo imeInfo, KeyboardLayout[] layoutList) {
         Arrays.sort(layoutList);
 
         // Check <VendorID, ProductID> matching for explicitly declared custom KCM files.
@@ -961,12 +1003,12 @@
             }
         }
 
-        InputMethodSubtype subtype = imeInfo.mImeSubtype;
-        // Can't auto select layout based on IME if subtype or language tag is null
-        if (subtype == null) {
+        if (imeInfo == null || imeInfo.mImeSubtypeHandle == null || imeInfo.mImeSubtype == null) {
+            // Can't auto select layout based on IME info is null
             return null;
         }
 
+        InputMethodSubtype subtype = imeInfo.mImeSubtype;
         // Check layout type, language tag information from IME for matching
         ULocale pkLocale = subtype.getPhysicalKeyboardHintLanguageTag();
         String pkLanguageTag =
@@ -1043,6 +1085,7 @@
         mNative.reloadKeyboardLayouts();
     }
 
+    @MainThread
     private void maybeUpdateNotification() {
         if (mConfiguredKeyboards.size() == 0) {
             hideKeyboardLayoutNotification();
@@ -1051,7 +1094,7 @@
         for (int i = 0; i < mConfiguredKeyboards.size(); i++) {
             // If we have a keyboard with no selected layouts, we should always show missing
             // layout notification even if there are other keyboards that are configured properly.
-            if (mConfiguredKeyboards.valueAt(i).isEmpty()) {
+            if (!mConfiguredKeyboards.valueAt(i).hasConfiguredLayouts()) {
                 showMissingKeyboardLayoutNotification();
                 return;
             }
@@ -1059,7 +1102,7 @@
         showConfiguredKeyboardLayoutNotification();
     }
 
-    // Must be called on handler.
+    @MainThread
     private void showMissingKeyboardLayoutNotification() {
         final Resources r = mContext.getResources();
         final String missingKeyboardLayoutNotificationContent = r.getString(
@@ -1084,6 +1127,7 @@
         }
     }
 
+    @MainThread
     private void showKeyboardLayoutNotification(@NonNull String intentTitle,
             @NonNull String intentContent, @Nullable InputDevice targetDevice) {
         final NotificationManager notificationManager = mContext.getSystemService(
@@ -1119,7 +1163,7 @@
                 notification, UserHandle.ALL);
     }
 
-    // Must be called on handler.
+    @MainThread
     private void hideKeyboardLayoutNotification() {
         NotificationManager notificationManager = mContext.getSystemService(
                 NotificationManager.class);
@@ -1132,6 +1176,7 @@
                 UserHandle.ALL);
     }
 
+    @MainThread
     private void showConfiguredKeyboardLayoutNotification() {
         final Resources r = mContext.getResources();
 
@@ -1144,8 +1189,8 @@
         }
 
         final InputDevice inputDevice = getInputDevice(mConfiguredKeyboards.keyAt(0));
-        final Set<String> selectedLayouts = mConfiguredKeyboards.valueAt(0);
-        if (inputDevice == null || selectedLayouts == null || selectedLayouts.isEmpty()) {
+        final KeyboardConfiguration config = mConfiguredKeyboards.valueAt(0);
+        if (inputDevice == null || !config.hasConfiguredLayouts()) {
             return;
         }
 
@@ -1153,10 +1198,11 @@
                 r.getString(
                         R.string.keyboard_layout_notification_selected_title,
                         inputDevice.getName()),
-                createConfiguredNotificationText(mContext, selectedLayouts),
+                createConfiguredNotificationText(mContext, config.getConfiguredLayouts()),
                 inputDevice);
     }
 
+    @MainThread
     private String createConfiguredNotificationText(@NonNull Context context,
             @NonNull Set<String> selectedLayouts) {
         final Resources r = context.getResources();
@@ -1199,6 +1245,9 @@
             case MSG_UPDATE_KEYBOARD_LAYOUTS:
                 updateKeyboardLayouts();
                 return true;
+            case MSG_CURRENT_IME_INFO_CHANGED:
+                onCurrentImeInfoChanged();
+                return true;
             default:
                 return false;
         }
@@ -1252,17 +1301,19 @@
         return imeInfoList;
     }
 
-    private String createLayoutKey(InputDeviceIdentifier identifier, int userId,
-            @NonNull InputMethodSubtypeHandle subtypeHandle) {
-        Objects.requireNonNull(subtypeHandle, "subtypeHandle must not be null");
-        return "layoutDescriptor:" + getLayoutDescriptor(identifier) + ",userId:" + userId
-                + ",subtypeHandle:" + subtypeHandle.toStringHandle();
+    private String createLayoutKey(InputDeviceIdentifier identifier, @Nullable ImeInfo imeInfo) {
+        if (imeInfo == null) {
+            return getLayoutDescriptor(identifier);
+        }
+        Objects.requireNonNull(imeInfo.mImeSubtypeHandle, "subtypeHandle must not be null");
+        return "layoutDescriptor:" + getLayoutDescriptor(identifier) + ",userId:" + imeInfo.mUserId
+                + ",subtypeHandle:" + imeInfo.mImeSubtypeHandle.toStringHandle();
     }
 
     private static boolean isLayoutCompatibleWithLanguageTag(KeyboardLayout layout,
             @NonNull String languageTag) {
         LocaleList layoutLocales = layout.getLocales();
-        if (layoutLocales.isEmpty()) {
+        if (layoutLocales.isEmpty() || TextUtils.isEmpty(languageTag)) {
             // KCM file doesn't have an associated language tag. This can be from
             // a 3rd party app so need to include it as a potential layout.
             return true;
@@ -1350,6 +1401,39 @@
         }
     }
 
+    private static class KeyboardConfiguration {
+        // If null or empty, it means no layout is configured for the device. And user needs to
+        // manually set up the device.
+        @Nullable
+        private Set<String> mConfiguredLayouts;
+
+        // If null, it means no layout is selected for the device.
+        @Nullable
+        private String mCurrentLayout;
+
+        private boolean hasConfiguredLayouts() {
+            return mConfiguredLayouts != null && !mConfiguredLayouts.isEmpty();
+        }
+
+        @Nullable
+        private Set<String> getConfiguredLayouts() {
+            return mConfiguredLayouts;
+        }
+
+        private void setConfiguredLayouts(Set<String> configuredLayouts) {
+            mConfiguredLayouts = configuredLayouts;
+        }
+
+        @Nullable
+        private String getCurrentLayout() {
+            return mCurrentLayout;
+        }
+
+        private void setCurrentLayout(String currentLayout) {
+            mCurrentLayout = currentLayout;
+        }
+    }
+
     private interface KeyboardLayoutVisitor {
         void visitKeyboardLayout(Resources resources,
                 int keyboardLayoutResId, KeyboardLayout layout);
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index a3d89e7..b708269 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -28,6 +28,7 @@
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
 import static android.app.ActivityManager.isProcStateConsideredInteraction;
+import static android.app.ActivityManager.printCapabilitiesSummary;
 import static android.app.ActivityManager.procStateToString;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
@@ -1133,6 +1134,7 @@
                 if (!callbackInfo.isPending) {
                     mUidEventHandler.obtainMessage(UID_MSG_STATE_CHANGED, callbackInfo)
                             .sendToTarget();
+                    callbackInfo.isPending = true;
                 }
             }
         }
@@ -1156,6 +1158,19 @@
             this.procStateSeq = procStateSeq;
             this.capability = capability;
         }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("{");
+            sb.append("uid=").append(uid).append(",");
+            sb.append("proc_state=").append(procStateToString(procState)).append(",");
+            sb.append("seq=").append(procStateSeq).append(",");
+            sb.append("cap="); printCapabilitiesSummary(sb, capability); sb.append(",");
+            sb.append("pending=").append(isPending);
+            sb.append("}");
+            return sb.toString();
+        }
     }
 
     final private BroadcastReceiver mPowerSaveWhitelistReceiver = new BroadcastReceiver() {
@@ -3982,31 +3997,31 @@
                 synchronized (mUidBlockedState) {
                     collectKeys(mUidBlockedState, knownUids);
                 }
+                synchronized (mUidStateCallbackInfos) {
+                    collectKeys(mUidStateCallbackInfos, knownUids);
+                }
 
                 fout.println("Status for all known UIDs:");
                 fout.increaseIndent();
                 size = knownUids.size();
                 for (int i = 0; i < size; i++) {
                     final int uid = knownUids.keyAt(i);
-                    fout.print("UID=");
-                    fout.print(uid);
+                    fout.print("UID", uid);
 
                     final UidState uidState = mUidState.get(uid);
-                    if (uidState == null) {
-                        fout.print(" state={null}");
-                    } else {
-                        fout.print(" state=");
-                        fout.print(uidState.toString());
-                    }
+                    fout.print("state", uidState);
 
                     synchronized (mUidBlockedState) {
                         final UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
-                        if (uidBlockedState == null) {
-                            fout.print(" blocked_state={null}");
-                        } else {
-                            fout.print(" blocked_state=");
-                            fout.print(uidBlockedState);
-                        }
+                        fout.print("blocked_state", uidBlockedState);
+                    }
+
+                    synchronized (mUidStateCallbackInfos) {
+                        final UidStateCallbackInfo callbackInfo = mUidStateCallbackInfos.get(uid);
+                        fout.println();
+                        fout.increaseIndent();
+                        fout.print("callback_info", callbackInfo);
+                        fout.decreaseIndent();
                     }
                     fout.println();
                 }
@@ -4052,27 +4067,49 @@
     }
 
     @VisibleForTesting
-    boolean isUidForeground(int uid) {
-        synchronized (mUidRulesFirstLock) {
-            return isProcStateAllowedWhileIdleOrPowerSaveMode(mUidState.get(uid));
+    @GuardedBy("mUidRulesFirstLock")
+    boolean isUidForegroundOnRestrictBackgroundUL(int uid) {
+        final UidState uidState = mUidState.get(uid);
+        if (isProcStateAllowedWhileOnRestrictBackground(uidState)) {
+            return true;
         }
+        // Check if there is any pending state change.
+        synchronized (mUidStateCallbackInfos) {
+            final UidStateCallbackInfo callbackInfo = mUidStateCallbackInfos.get(uid);
+            final long prevProcStateSeq = uidState != null ? uidState.procStateSeq : -1;
+            if (callbackInfo != null && callbackInfo.isPending
+                    && callbackInfo.procStateSeq >= prevProcStateSeq) {
+                return isProcStateAllowedWhileOnRestrictBackground(callbackInfo.procState,
+                        callbackInfo.capability);
+            }
+        }
+        return false;
     }
 
+    @VisibleForTesting
     @GuardedBy("mUidRulesFirstLock")
-    private boolean isUidForegroundOnRestrictBackgroundUL(int uid) {
+    boolean isUidForegroundOnRestrictPowerUL(int uid) {
         final UidState uidState = mUidState.get(uid);
-        return isProcStateAllowedWhileOnRestrictBackground(uidState);
-    }
-
-    @GuardedBy("mUidRulesFirstLock")
-    private boolean isUidForegroundOnRestrictPowerUL(int uid) {
-        final UidState uidState = mUidState.get(uid);
-        return isProcStateAllowedWhileIdleOrPowerSaveMode(uidState);
+        if (isProcStateAllowedWhileIdleOrPowerSaveMode(uidState)) {
+            return true;
+        }
+        // Check if there is any pending state change.
+        synchronized (mUidStateCallbackInfos) {
+            final UidStateCallbackInfo callbackInfo = mUidStateCallbackInfos.get(uid);
+            final long prevProcStateSeq = uidState != null ? uidState.procStateSeq : -1;
+            if (callbackInfo != null && callbackInfo.isPending
+                    && callbackInfo.procStateSeq >= prevProcStateSeq) {
+                return isProcStateAllowedWhileIdleOrPowerSaveMode(callbackInfo.procState,
+                        callbackInfo.capability);
+            }
+        }
+        return false;
     }
 
     @GuardedBy("mUidRulesFirstLock")
     private boolean isUidTop(int uid) {
         final UidState uidState = mUidState.get(uid);
+        // TODO: Consider taking pending uid state change into account.
         return isProcStateAllowedWhileInLowPowerStandby(uidState);
     }
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 0532a79..247a5c0 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2987,6 +2987,7 @@
                 }
                 break;
             case KeyEvent.KEYCODE_H:
+            case KeyEvent.KEYCODE_ENTER:
                 if (event.isMetaPressed()) {
                     return handleHomeShortcuts(displayId, focusedToken, event);
                 }
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 8bbcd27..c40d72c 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -807,11 +807,8 @@
                 if (under != null) {
                     under.returningOptions = safeOptions != null ? safeOptions.getOptions(r) : null;
                 }
-                // Create a transition if the activity is playing in case the current activity
-                // didn't commit invisible. That's because if this activity has changed its
-                // visibility while playing transition, there won't able to commit visibility until
-                // the running transition finish.
-                final Transition transition = r.mTransitionController.inPlayingTransition(r)
+                // Create a transition to make sure the activity change is collected.
+                final Transition transition = r.mTransitionController.isShellTransitionsEnabled()
                         && !r.mTransitionController.isCollecting()
                         ? r.mTransitionController.createTransition(TRANSIT_TO_FRONT) : null;
                 final boolean changed = r.setOccludesParent(false);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f478e9b..8bfa426 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2267,6 +2267,12 @@
         if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
             return WmDisplayCutout.NO_CUTOUT;
         }
+        if (displayWidth == displayHeight) {
+            Slog.w(TAG, "Ignore cutout because display size is square: " + displayWidth);
+            // Avoid UnsupportedOperationException because DisplayCutout#computeSafeInsets doesn't
+            // support square size.
+            return WmDisplayCutout.NO_CUTOUT;
+        }
         if (rotation == ROTATION_0) {
             return WmDisplayCutout.computeSafeInsets(
                     cutout, displayWidth, displayHeight);
@@ -3087,13 +3093,9 @@
 
         mIsSizeForced = mInitialDisplayWidth != width || mInitialDisplayHeight != height;
         if (mIsSizeForced) {
-            // Set some sort of reasonable bounds on the size of the display that we will try
-            // to emulate.
-            final int minSize = 200;
-            final int maxScale = 3;
-            final int maxSize = Math.max(mInitialDisplayWidth, mInitialDisplayHeight) * maxScale;
-            width = Math.min(Math.max(width, minSize), maxSize);
-            height = Math.min(Math.max(height, minSize), maxSize);
+            final Point size = getValidForcedSize(width, height);
+            width = size.x;
+            height = size.y;
         }
 
         Slog.i(TAG_WM, "Using new display size: " + width + "x" + height);
@@ -3108,6 +3110,16 @@
         mWmService.mDisplayWindowSettings.setForcedSize(this, width, height);
     }
 
+    /** Returns a reasonable size for setting forced display size. */
+    Point getValidForcedSize(int w, int h) {
+        final int minSize = 200;
+        final int maxScale = 3;
+        final int maxSize = Math.max(mInitialDisplayWidth, mInitialDisplayHeight) * maxScale;
+        w = Math.min(Math.max(w, minSize), maxSize);
+        h = Math.min(Math.max(h, minSize), maxSize);
+        return new Point(w, h);
+    }
+
     DisplayCutout loadDisplayCutout(int displayWidth, int displayHeight) {
         if (mDisplayPolicy == null || mInitialDisplayCutout == null) {
             return null;
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 20048ce..a5e652c 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -946,6 +946,10 @@
     }
 
     void freezeRotation(int rotation) {
+        if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
+            rotation = RotationUtils.reverseRotationDirectionAroundZAxis(rotation);
+        }
+
         rotation = (rotation == -1) ? mRotation : rotation;
         setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation);
     }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b314ed1..54dfdd9 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -107,6 +107,11 @@
  * Represents a logical transition. This keeps track of all the changes associated with a logical
  * WM state -> state transition.
  * @see TransitionController
+ *
+ * In addition to tracking individual container changes, this also tracks ordering-changes (just
+ * on-top for now). However, since order is a "global" property, the mechanics of order-change
+ * detection/reporting is non-trivial when transitions are collecting in parallel. See
+ * {@link #collectOrderChanges} for more details.
  */
 class Transition implements BLASTSyncEngine.TransactionReadyListener {
     private static final String TAG = "Transition";
@@ -192,6 +197,12 @@
     private final ArrayList<Task> mOnTopTasksStart = new ArrayList<>();
 
     /**
+     * The (non alwaysOnTop) tasks which were on-top of their display when this transition became
+     * ready (via setReady, not animation-ready).
+     */
+    private final ArrayList<Task> mOnTopTasksAtReady = new ArrayList<>();
+
+    /**
      * Set of participating windowtokens (activity/wallpaper) which are visible at the end of
      * the transition animation.
      */
@@ -244,6 +255,36 @@
      */
     boolean mIsPlayerEnabled = true;
 
+    /** This transition doesn't run in parallel. */
+    static final int PARALLEL_TYPE_NONE = 0;
+
+    /** Any 2 transitions of this type can run in parallel with each other. Used for testing. */
+    static final int PARALLEL_TYPE_MUTUAL = 1;
+
+    @IntDef(prefix = { "PARALLEL_TYPE_" }, value = {
+            PARALLEL_TYPE_NONE,
+            PARALLEL_TYPE_MUTUAL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ParallelType {}
+
+    /**
+     * What category of parallel-collect support this transition has. The value of this is used
+     * by {@link TransitionController} to determine which transitions can collect in parallel. If
+     * a transition can collect in parallel, it means that it will start collecting as soon as the
+     * prior collecting transition is {@link #isPopulated}. This is a shortcut for supporting
+     * a couple specific situations before we have full-fledged support for parallel transitions.
+     */
+    @ParallelType int mParallelCollectType = PARALLEL_TYPE_NONE;
+
+    /**
+     * A "Track" is a set of animations which must cooperate with each other to play smoothly. If
+     * animations can play independently of each other, then they can be in different tracks. If
+     * a transition must cooperate with transitions in >1 other track, then it must be marked
+     * FLAG_SYNC and it will end-up flushing all animations before it starts.
+     */
+    int mAnimationTrack = 0;
+
     Transition(@TransitionType int type, @TransitionFlags int flags,
             TransitionController controller, BLASTSyncEngine syncEngine) {
         mType = type;
@@ -447,7 +488,8 @@
             throw new IllegalStateException("Attempting to re-use a transition");
         }
         mState = STATE_COLLECTING;
-        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG, false /* parallel */);
+        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG,
+                mParallelCollectType != PARALLEL_TYPE_NONE);
         mSyncEngine.setSyncMethod(mSyncId, TransitionController.SYNC_METHOD);
 
         mLogger.mSyncId = mSyncId;
@@ -718,8 +760,15 @@
         final boolean ready = mReadyTracker.allReady();
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                 "Set transition ready=%b %d", ready, mSyncId);
-        mSyncEngine.setReady(mSyncId, ready);
-        if (ready) mLogger.mReadyTimeNs = SystemClock.elapsedRealtimeNanos();
+        boolean changed = mSyncEngine.setReady(mSyncId, ready);
+        if (changed && ready) {
+            mLogger.mReadyTimeNs = SystemClock.elapsedRealtimeNanos();
+            mOnTopTasksAtReady.clear();
+            for (int i = 0; i < mTargetDisplays.size(); ++i) {
+                addOnTopTasks(mTargetDisplays.get(i), mOnTopTasksAtReady);
+            }
+            mController.onTransitionPopulated(this);
+        }
     }
 
     /**
@@ -737,6 +786,11 @@
         return mReadyTracker.allReady();
     }
 
+    /** This transition has all of its expected participants. */
+    boolean isPopulated() {
+        return mState >= STATE_STARTED && mReadyTracker.allReady();
+    }
+
     /**
      * Build a transaction that "resets" all the re-parenting and layer changes. This is
      * intended to be applied at the end of the transition but before the finish callback. This
@@ -1211,7 +1265,7 @@
                 : mController.mAtm.mRootWindowContainer.getDefaultDisplay();
 
         if (mState == STATE_ABORT) {
-            mController.abort(this);
+            mController.onAbort(this);
             primaryDisplay.getPendingTransaction().merge(transaction);
             mSyncId = -1;
             mOverrideOptions = null;
@@ -1222,13 +1276,15 @@
         mState = STATE_PLAYING;
         mStartTransaction = transaction;
         mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
-        mController.moveToPlaying(this);
 
         // Flags must be assigned before calculateTransitionInfo. Otherwise it won't take effect.
         if (primaryDisplay.isKeyguardLocked()) {
             mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
         }
-        collectOrderChanges();
+
+        // This is the only (or last) transition that is collecting, so we need to report any
+        // leftover order changes.
+        collectOrderChanges(mController.mWaitingTransitions.isEmpty());
 
         // Resolve the animating targets from the participants.
         mTargets = calculateTargets(mParticipants, mChanges);
@@ -1236,6 +1292,9 @@
         mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets);
         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
         info.setDebugId(mSyncId);
+        mController.assignTrack(this, info);
+
+        mController.moveToPlaying(this);
 
         // Repopulate the displays based on the resolved targets.
         mTargetDisplays.clear();
@@ -1383,24 +1442,70 @@
         info.releaseAnimSurfaces();
     }
 
-    /** Collect tasks which moved-to-top but didn't change otherwise. */
+    /**
+     * Collect tasks which moved-to-top as part of this transition. This also updates the
+     * controller's latest-reported when relevant.
+     *
+     * This is a non-trivial operation because transition can collect in parallel; however, it can
+     * be made tenable by acknowledging that the "setup" part of collection (phase 1) is still
+     * globally serial; so, we can build some reasonable rules around it.
+     *
+     * First, we record the "start" on-top state (to compare against). Then, when this becomes
+     * ready (via allReady, NOT onTransactionReady), we also record the "onReady" on-top state
+     * -- the idea here is that upon "allReady", all the actual WM changes should be done and we
+     * are now just waiting for window content to become ready (finish drawing).
+     *
+     * Then, in this function (during onTransactionReady), we compare the two orders and include
+     * any changes to the order in the reported transition-info. Unfortunately, because of parallel
+     * collection, the order can change in unexpected ways by now. To resolve this, we ALSO keep a
+     * global "latest reported order" in TransitionController and use that to make decisions.
+     */
     @VisibleForTesting
-    void collectOrderChanges() {
+    void collectOrderChanges(boolean reportCurrent) {
         if (mOnTopTasksStart.isEmpty()) return;
-        final ArrayList<Task> onTopTasksEnd = new ArrayList<>();
-        for (int i = 0; i < mTargetDisplays.size(); ++i) {
-            addOnTopTasks(mTargetDisplays.get(i), onTopTasksEnd);
-        }
-        for (int i = 0; i < onTopTasksEnd.size(); ++i) {
-            final Task task = onTopTasksEnd.get(i);
+        boolean includesOrderChange = false;
+        for (int i = 0; i < mOnTopTasksAtReady.size(); ++i) {
+            final Task task = mOnTopTasksAtReady.get(i);
             if (mOnTopTasksStart.contains(task)) continue;
-            mParticipants.add(task);
-            int changeIdx = mChanges.indexOfKey(task);
-            if (changeIdx < 0) {
-                mChanges.put(task, new ChangeInfo(task));
-                changeIdx = mChanges.indexOfKey(task);
+            includesOrderChange = true;
+            break;
+        }
+        if (!includesOrderChange && !reportCurrent) {
+            // This transition doesn't include an order change, so if it isn't required to report
+            // the current focus (eg. it's the last of a cluster of transitions), then don't
+            // report.
+            return;
+        }
+        // The transition included an order change, but it may not be up-to-date, so grab the
+        // latest state and compare with the last reported state (or our start state if no
+        // reported state exists).
+        ArrayList<Task> onTopTasksEnd = new ArrayList<>();
+        for (int d = 0; d < mTargetDisplays.size(); ++d) {
+            addOnTopTasks(mTargetDisplays.get(d), onTopTasksEnd);
+            final int displayId = mTargetDisplays.get(d).mDisplayId;
+            ArrayList<Task> reportedOnTop = mController.mLatestOnTopTasksReported.get(displayId);
+            for (int i = onTopTasksEnd.size() - 1; i >= 0; --i) {
+                final Task task = onTopTasksEnd.get(i);
+                if (task.getDisplayId() != displayId) continue;
+                // If it didn't change since last report, don't report
+                if (reportedOnTop == null) {
+                    if (mOnTopTasksStart.contains(task)) continue;
+                } else if (reportedOnTop.contains(task)) {
+                    continue;
+                }
+                // Need to report it.
+                mParticipants.add(task);
+                int changeIdx = mChanges.indexOfKey(task);
+                if (changeIdx < 0) {
+                    mChanges.put(task, new ChangeInfo(task));
+                    changeIdx = mChanges.indexOfKey(task);
+                }
+                mChanges.valueAt(changeIdx).mFlags |= ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP;
             }
-            mChanges.valueAt(changeIdx).mFlags |= ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP;
+            // Swap in the latest on-top tasks.
+            mController.mLatestOnTopTasksReported.put(displayId, onTopTasksEnd);
+            onTopTasksEnd = reportedOnTop != null ? reportedOnTop : new ArrayList<>();
+            onTopTasksEnd.clear();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index e8e4792..8af037b 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -39,6 +39,7 @@
 import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl;
@@ -60,7 +61,34 @@
 import java.util.function.LongConsumer;
 
 /**
- * Handles all the aspects of recording and synchronizing transitions.
+ * Handles all the aspects of recording (collecting) and synchronizing transitions. This is only
+ * concerned with the WM changes. The actual animations are handled by the Player.
+ *
+ * Currently, only 1 transition can be the primary "collector" at a time. This is because WM changes
+ * are still performed in a "global" manner. However, collecting can actually be broken into
+ * two phases:
+ *    1. Actually making WM changes and recording the participating containers.
+ *    2. Waiting for the participating containers to become ready (eg. redrawing content).
+ * Because (2) takes most of the time AND doesn't change WM, we can actually have multiple
+ * transitions in phase (2) concurrently with one in phase (1). We refer to this arrangement as
+ * "parallel" collection even though there is still only ever 1 transition actually able to gain
+ * participants.
+ *
+ * Parallel collection happens when the "primary collector" has finished "setup" (phase 1) and is
+ * just waiting. At this point, another transition can start collecting. When this happens, the
+ * first transition is moved to a "waiting" list and the new transition becomes the "primary
+ * collector". If at any time, the "primary collector" moves to playing before one of the waiting
+ * transitions, then the first waiting transition will move back to being the "primary collector".
+ * This maintains the "global"-like abstraction that the rest of WM currently expects.
+ *
+ * When a transition move-to-playing, we check it against all other playing transitions. If it
+ * doesn't overlap with them, it can also animate in parallel. In this case it will be assigned a
+ * new "track". "tracks" are a way to communicate to the player about which transitions need to be
+ * played serially with each-other. So, if we find that a transition overlaps with other transitions
+ * in one track, the transition will be assigned to that track. If, however, the transition overlaps
+ * with transition in >1 track, we will actually just mark it as SYNC meaning it can't actually
+ * play until all prior transition animations finish. This is heavy-handed because it is a fallback
+ * situation and supporting something fancier would be unnecessarily complicated.
  */
 class TransitionController {
     private static final String TAG = "TransitionController";
@@ -109,6 +137,7 @@
      * removed from this list.
      */
     private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>();
+    int mTrackCount = 0;
 
     /** The currently finishing transition. */
     Transition mFinishingTransition;
@@ -143,10 +172,26 @@
 
     private final ArrayList<QueuedTransition> mQueuedTransitions = new ArrayList<>();
 
-    /** The transition currently being constructed (collecting participants). */
+    /**
+     * The transition currently being constructed (collecting participants). Unless interrupted,
+     * all WM changes will go into this.
+     */
     private Transition mCollectingTransition = null;
 
     /**
+     * The transitions that are complete but still waiting for participants to become ready
+     */
+    final ArrayList<Transition> mWaitingTransitions = new ArrayList<>();
+
+    /**
+     * The (non alwaysOnTop) tasks which were reported as on-top of their display most recently
+     * within a cluster of simultaneous transitions. If tasks are nested, all the tasks that are
+     * parents of the on-top task are also included. This is used to decide which transitions
+     * report which on-top changes.
+     */
+    final SparseArray<ArrayList<Task>> mLatestOnTopTasksReported = new SparseArray<>();
+
+    /**
      * `true` when building surface layer order for the finish transaction. We want to prevent
      * wm from touching z-order of surfaces during transitions, but we still need to be able to
      * calculate the layers for the finishTransaction. So, when assigning layers into the finish
@@ -199,6 +244,11 @@
             mPlayingTransitions.get(i).cleanUpOnFailure();
         }
         mPlayingTransitions.clear();
+        // Clean up waiting transitions first since they technically started first.
+        for (int i = 0; i < mWaitingTransitions.size(); ++i) {
+            mWaitingTransitions.get(i).abort();
+        }
+        mWaitingTransitions.clear();
         if (mCollectingTransition != null) {
             mCollectingTransition.abort();
         }
@@ -223,8 +273,9 @@
             throw new IllegalStateException("Shell Transitions not enabled");
         }
         if (mCollectingTransition != null) {
-            throw new IllegalStateException("Simultaneous transition collection not supported"
-                    + " yet. Use {@link #createPendingTransition} for explicit queueing.");
+            throw new IllegalStateException("Trying to directly start transition collection while "
+                    + " collection is already ongoing. Use {@link #startCollectOrQueue} if"
+                    + " possible.");
         }
         Transition transit = new Transition(type, flags, this, mSyncEngine);
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s", transit);
@@ -318,7 +369,12 @@
      *                      This is {@code false} once a transition is playing.
      */
     boolean isCollecting(@NonNull WindowContainer wc) {
-        return mCollectingTransition != null && mCollectingTransition.mParticipants.contains(wc);
+        if (mCollectingTransition == null) return false;
+        if (mCollectingTransition.mParticipants.contains(wc)) return true;
+        for (int i = 0; i < mWaitingTransitions.size(); ++i) {
+            if (mWaitingTransitions.get(i).mParticipants.contains(wc)) return true;
+        }
+        return false;
     }
 
     /**
@@ -327,7 +383,11 @@
      */
     boolean inCollectingTransition(@NonNull WindowContainer wc) {
         if (!isCollecting()) return false;
-        return mCollectingTransition.isInTransition(wc);
+        if (mCollectingTransition.isInTransition(wc)) return true;
+        for (int i = 0; i < mWaitingTransitions.size(); ++i) {
+            if (mWaitingTransitions.get(i).isInTransition(wc)) return true;
+        }
+        return false;
     }
 
     /**
@@ -369,6 +429,9 @@
         if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) {
             return true;
         }
+        for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+            if (mWaitingTransitions.get(i).isOnDisplay(dc)) return true;
+        }
         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
             if (mPlayingTransitions.get(i).isOnDisplay(dc)) return true;
         }
@@ -379,6 +442,9 @@
         if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) {
             return true;
         }
+        for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+            if (mWaitingTransitions.get(i).isInTransientHide(task)) return true;
+        }
         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
             if (mPlayingTransitions.get(i).isInTransientHide(task)) return true;
         }
@@ -754,6 +820,8 @@
         mRunningLock.doNotifyLocked();
         // Run state-validation checks when no transitions are active anymore.
         if (!inTransition()) {
+            // Can reset track-count now that everything is idle.
+            mTrackCount = 0;
             validateStates();
         }
     }
@@ -771,12 +839,39 @@
         mStateValidators.clear();
     }
 
+    /**
+     * Called when the transition has a complete set of participants for its operation. In other
+     * words, it is when the transition is "ready" but is still waiting for participants to draw.
+     */
+    void onTransitionPopulated(Transition transition) {
+        tryStartCollectFromQueue();
+    }
+
+    private boolean canStartCollectingNow(Transition queued) {
+        if (mCollectingTransition == null) return true;
+        // Population (collect until ready) is still serialized, so always wait for that.
+        if (!mCollectingTransition.isPopulated()) return false;
+        // Check if queued *can* be independent with all collecting/waiting transitions.
+        if (!getCanBeIndependent(mCollectingTransition, queued)) return false;
+        for (int i = 0; i < mWaitingTransitions.size(); ++i) {
+            if (!getCanBeIndependent(mWaitingTransitions.get(i), queued)) return false;
+        }
+        return true;
+    }
+
     void tryStartCollectFromQueue() {
         if (mQueuedTransitions.isEmpty()) return;
         // Only need to try the next one since, even when transition can collect in parallel,
         // they still need to serialize on readiness.
         final QueuedTransition queued = mQueuedTransitions.get(0);
-        if (mCollectingTransition != null || mSyncEngine.hasActiveSync()) {
+        if (mCollectingTransition != null) {
+            // If it's a legacy sync, then it needs to wait until there is no collecting transition.
+            if (queued.mTransition == null) return;
+            if (!canStartCollectingNow(queued.mTransition)) return;
+            mWaitingTransitions.add(mCollectingTransition);
+            mCollectingTransition = null;
+        } else if (mSyncEngine.hasActiveSync()) {
+            // A legacy transition is on-going, so we must wait.
             return;
         }
         mQueuedTransitions.remove(0);
@@ -797,16 +892,81 @@
     }
 
     void moveToPlaying(Transition transition) {
-        if (transition != mCollectingTransition) {
-            throw new IllegalStateException("Trying to move non-collecting transition to playing");
+        if (transition == mCollectingTransition) {
+            mCollectingTransition = null;
+            if (!mWaitingTransitions.isEmpty()) {
+                mCollectingTransition = mWaitingTransitions.remove(0);
+            }
+            if (mCollectingTransition == null) {
+                // nothing collecting anymore, so clear order records.
+                mLatestOnTopTasksReported.clear();
+            }
+        } else {
+            if (!mWaitingTransitions.remove(transition)) {
+                throw new IllegalStateException("Trying to move non-collecting transition to"
+                        + "playing " + transition.getSyncId());
+            }
         }
-        mCollectingTransition = null;
         mPlayingTransitions.add(transition);
         updateRunningRemoteAnimation(transition, true /* isPlaying */);
         mTransitionTracer.logState(transition);
         // Sync engine should become idle after this, so the idle listener will check the queue.
     }
 
+    /**
+     * Checks if the `queued` transition has the potential to run independently of the
+     * `collecting` transition. It may still ultimately block in sync-engine or become dependent
+     * in {@link #getIsIndependent} later.
+     */
+    boolean getCanBeIndependent(Transition collecting, Transition queued) {
+        if (queued.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL
+                && collecting.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Checks if `incoming` transition can run independently of `running` transition assuming that
+     * `running` is playing based on its current state.
+     */
+    static boolean getIsIndependent(Transition running, Transition incoming) {
+        if (running.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL
+                && incoming.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL) {
+            return true;
+        }
+        return false;
+    }
+
+    void assignTrack(Transition transition, TransitionInfo info) {
+        int track = -1;
+        boolean sync = false;
+        for (int i = 0; i < mPlayingTransitions.size(); ++i) {
+            // ignore ourself obviously
+            if (mPlayingTransitions.get(i) == transition) continue;
+            if (getIsIndependent(mPlayingTransitions.get(i), transition)) continue;
+            if (track >= 0) {
+                // At this point, transition overlaps with multiple tracks, so just wait for
+                // everything
+                sync = true;
+                break;
+            }
+            track = mPlayingTransitions.get(i).mAnimationTrack;
+        }
+        if (sync) {
+            track = 0;
+        }
+        if (track < 0) {
+            // Didn't overlap with anything, so give it its own track
+            track = mTrackCount;
+        }
+        if (sync) {
+            info.setFlags(info.getFlags() | TransitionInfo.FLAG_SYNC);
+        }
+        info.setTrack(track);
+        mTrackCount = Math.max(mTrackCount, track + 1);
+    }
+
     void updateAnimatingState(SurfaceControl.Transaction t) {
         final boolean animatingState = !mPlayingTransitions.isEmpty()
                     || (mCollectingTransition != null && mCollectingTransition.isStarted());
@@ -842,14 +1002,27 @@
         mRemotePlayer.update(delegate, isPlaying, true /* predict */);
     }
 
-    void abort(Transition transition) {
+    /** Called when a transition is aborted. This should only be called by {@link Transition} */
+    void onAbort(Transition transition) {
         if (transition != mCollectingTransition) {
-            throw new IllegalStateException("Too late to abort.");
+            int waitingIdx = mWaitingTransitions.indexOf(transition);
+            if (waitingIdx < 0) {
+                throw new IllegalStateException("Too late for abort.");
+            }
+            mWaitingTransitions.remove(waitingIdx);
+        } else {
+            mCollectingTransition = null;
+            if (!mWaitingTransitions.isEmpty()) {
+                mCollectingTransition = mWaitingTransitions.remove(0);
+            }
+            if (mCollectingTransition == null) {
+                // nothing collecting anymore, so clear order records.
+                mLatestOnTopTasksReported.clear();
+            }
         }
-        transition.abort();
-        mCollectingTransition = null;
         mTransitionTracer.logState(transition);
-        // abort will call through the normal finish paths and thus check the queue.
+        // This is called during Transition.abort whose codepath will eventually check the queue
+        // via sync-engine idle.
     }
 
     /**
@@ -956,7 +1129,17 @@
             return false;
         }
         if (mSyncEngine.hasActiveSync()) {
-            if (!isCollecting()) {
+            if (isCollecting()) {
+                // Check if we can run in parallel here.
+                if (canStartCollectingNow(transit)) {
+                    // start running in parallel.
+                    mWaitingTransitions.add(mCollectingTransition);
+                    mCollectingTransition = null;
+                    moveToCollecting(transit);
+                    onStartCollect.onCollectStarted(false /* deferred */);
+                    return true;
+                }
+            } else {
                 Slog.w(TAG, "Ongoing Sync outside of transition.");
             }
             queueTransition(transit, onStartCollect);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8822193..40b8274 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5780,10 +5780,12 @@
         if (sizeStr != null && sizeStr.length() > 0) {
             final int pos = sizeStr.indexOf(',');
             if (pos > 0 && sizeStr.lastIndexOf(',') == pos) {
-                int width, height;
                 try {
-                    width = Integer.parseInt(sizeStr.substring(0, pos));
-                    height = Integer.parseInt(sizeStr.substring(pos + 1));
+                    final Point size = displayContent.getValidForcedSize(
+                            Integer.parseInt(sizeStr.substring(0, pos)),
+                            Integer.parseInt(sizeStr.substring(pos + 1)));
+                    final int width = size.x;
+                    final int height = size.y;
                     if (displayContent.mBaseDisplayWidth != width
                             || displayContent.mBaseDisplayHeight != height) {
                         ProtoLog.i(WM_ERROR, "FORCED DISPLAY SIZE: %dx%d", width, height);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f6bc93a..40024f1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3575,7 +3575,9 @@
     void handleStartUser(int userId) {
         synchronized (getLockObject()) {
             pushScreenCapturePolicy(userId);
-            pushUserControlDisabledPackagesLocked(userId);
+            if (!isPolicyEngineForFinanceFlagEnabled()) {
+                pushUserControlDisabledPackagesLocked(userId);
+            }
         }
         pushUserRestrictions(userId);
         // When system user is started (device boot), load cache for all users.
@@ -6038,7 +6040,7 @@
     @Override
     public void lockNow(int flags, String callerPackageName, boolean parent) {
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(callerPackageName);
         } else {
             caller = getCallerIdentity();
@@ -6050,7 +6052,7 @@
             ActiveAdmin admin;
             // Make sure the caller has any active admin with the right policy or
             // the required permission.
-            if (isPermissionCheckFlagEnabled()) {
+            if (isPolicyEngineForFinanceFlagEnabled()) {
                 admin = enforcePermissionAndGetEnforcingAdmin(
                         /* admin= */ null,
                         /* permission= */ MANAGE_DEVICE_POLICY_LOCK,
@@ -7513,6 +7515,7 @@
         boolean success = false;
         try {
             if (getCurrentForegroundUserId() == userId) {
+                // TODO: We need to special case headless here as we can't switch to the system user
                 mInjector.getIActivityManager().switchUser(UserHandle.USER_SYSTEM);
             }
 
@@ -7520,7 +7523,8 @@
             if (!success) {
                 Slogf.w(LOG_TAG, "Couldn't remove user " + userId);
             } else if (isManagedProfile(userId) && !wipeSilently) {
-                sendWipeProfileNotification(wipeReasonForUser);
+                sendWipeProfileNotification(wipeReasonForUser,
+                        UserHandle.of(getProfileParentId(userId)));
             }
         } catch (RemoteException re) {
             // Shouldn't happen
@@ -7868,7 +7872,7 @@
         });
     }
 
-    private void sendWipeProfileNotification(String wipeReasonForUser) {
+    private void sendWipeProfileNotification(String wipeReasonForUser, UserHandle user) {
         Notification notification =
                 new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
                         .setSmallIcon(android.R.drawable.stat_sys_warning)
@@ -7877,7 +7881,8 @@
                         .setColor(mContext.getColor(R.color.system_notification_accent_color))
                         .setStyle(new Notification.BigTextStyle().bigText(wipeReasonForUser))
                         .build();
-        mInjector.getNotificationManager().notify(SystemMessage.NOTE_PROFILE_WIPED, notification);
+        mInjector.getNotificationManager().notifyAsUser(
+                /* tag= */ null, SystemMessage.NOTE_PROFILE_WIPED, notification, user);
     }
 
     private String getWorkProfileDeletedTitle() {
@@ -8901,13 +8906,13 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             // The effect of this policy is device-wide.
             enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
         } else {
@@ -8935,13 +8940,13 @@
             return false;
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             enforceCanQuery(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
         } else {
             Objects.requireNonNull(who, "ComponentName is null");
@@ -8970,7 +8975,7 @@
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             // The effect of this policy is device-wide.
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     who,
@@ -9010,13 +9015,13 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             // The effect of this policy is device-wide.
             enforceCanQuery(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL);
         } else {
@@ -9319,7 +9324,7 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
@@ -9329,7 +9334,7 @@
         final int userHandle = caller.getUserId();
         int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
         synchronized (getLockObject()) {
-            if (isPermissionCheckFlagEnabled()) {
+            if (isPolicyEngineForFinanceFlagEnabled()) {
                 // SUPPORT USES_POLICY_DISABLE_KEYGUARD_FEATURES
                 EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
                         who, MANAGE_DEVICE_POLICY_KEYGUARD, caller.getPackageName(),
@@ -9408,7 +9413,7 @@
 
         synchronized (getLockObject()) {
             if (who != null) {
-                if (isPermissionCheckFlagEnabled()) {
+                if (isPolicyEngineForFinanceFlagEnabled()) {
                     EnforcingAdmin admin = getEnforcingAdminForCaller(
                             who, who.getPackageName());
                     Integer features = mDevicePolicyEngine.getLocalPolicySetByAdmin(
@@ -9422,7 +9427,7 @@
                 }
             }
 
-            if (isPermissionCheckFlagEnabled()) {
+            if (isPolicyEngineForFinanceFlagEnabled()) {
                 Integer features = mDevicePolicyEngine.getResolvedPolicy(
                         PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
                         affectedUserId);
@@ -10062,7 +10067,9 @@
         setNetworkLoggingActiveInternal(false);
         deleteTransferOwnershipBundleLocked(userId);
         toggleBackupServiceActive(UserHandle.USER_SYSTEM, true);
-        pushUserControlDisabledPackagesLocked(userId);
+        if (!isPolicyEngineForFinanceFlagEnabled()) {
+            pushUserControlDisabledPackagesLocked(userId);
+        }
         setGlobalSettingDeviceOwnerType(DEVICE_OWNER_TYPE_DEFAULT);
 
         if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
@@ -11617,7 +11624,7 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS);
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     who,
                     MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
@@ -12993,7 +13000,7 @@
             String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
                     who,
                     MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
@@ -13063,7 +13070,7 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         ActiveAdmin admin;
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     who,
                     MANAGE_DEVICE_POLICY_PACKAGE_STATE,
@@ -13160,7 +13167,7 @@
     public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             enforcePermission(
                     MANAGE_DEVICE_POLICY_PACKAGE_STATE,
                     caller.getPackageName(),
@@ -13376,7 +13383,7 @@
             throw new IllegalStateException("Feature flag is not enabled.");
         }
         if (isDeviceOwner(caller) || isProfileOwner(caller)) {
-            throw new IllegalStateException("Admins are not allowed to call this API.");
+            throw new SecurityException("Admins are not allowed to call this API.");
         }
         if (!mInjector.isChangeEnabled(
                 ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
@@ -13766,7 +13773,7 @@
             boolean hidden, boolean parent) {
         CallerIdentity caller = getCallerIdentity(who, callerPackage);
         final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             // TODO: We need to ensure the delegate with DELEGATION_PACKAGE_ACCESS can do this
             enforcePermission(MANAGE_DEVICE_POLICY_PACKAGE_STATE, caller.getPackageName(), userId);
         } else {
@@ -13785,7 +13792,7 @@
         boolean result;
         synchronized (getLockObject()) {
             if (parent) {
-                if (!isPermissionCheckFlagEnabled()) {
+                if (!isPolicyEngineForFinanceFlagEnabled()) {
                     Preconditions.checkCallAuthorization(
                             isProfileOwnerOfOrganizationOwnedDevice(
                                     caller.getUserId()) && isManagedProfile(caller.getUserId()));
@@ -13802,7 +13809,7 @@
                 Slogf.v(LOG_TAG, "calling pm.setApplicationHiddenSettingAsUser(%s, %b, %d)",
                         packageName, hidden, userId);
             }
-            if (isPermissionCheckFlagEnabled()) {
+            if (isPolicyEngineForFinanceFlagEnabled()) {
                 EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage);
                 mDevicePolicyEngine.setLocalPolicy(
                         PolicyDefinition.APPLICATION_HIDDEN(packageName),
@@ -13841,7 +13848,7 @@
             String packageName, boolean parent) {
         CallerIdentity caller = getCallerIdentity(who, callerPackage);
         int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             // TODO: Also support DELEGATION_PACKAGE_ACCESS
             enforcePermission(MANAGE_DEVICE_POLICY_PACKAGE_STATE, caller.getPackageName(), userId);
         } else {
@@ -13853,7 +13860,7 @@
 
         synchronized (getLockObject()) {
             if (parent) {
-                if (!isPermissionCheckFlagEnabled()) {
+                if (!isPolicyEngineForFinanceFlagEnabled()) {
                     Preconditions.checkCallAuthorization(
                             isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId())
                                     && isManagedProfile(caller.getUserId()));
@@ -14042,13 +14049,13 @@
             return;
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
         synchronized (getLockObject()) {
-            if (isPermissionCheckFlagEnabled()) {
+            if (isPolicyEngineForFinanceFlagEnabled()) {
                 int affectedUser = getAffectedUser(parent);
                 EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                         who,
@@ -14111,7 +14118,7 @@
         CallerIdentity caller;
         Preconditions.checkArgumentNonnegative(userId, "Invalid userId");
         final ArraySet<String> resultSet = new ArraySet<>();
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             int affectedUser = parent ? getProfileParentId(userId) : userId;
             caller = getCallerIdentity(callerPackageName);
             if (!hasPermission(MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
@@ -14744,24 +14751,24 @@
             synchronized (getLockObject()) {
                 enforcingAdmin = enforceCanCallLockTaskLocked(who, caller.getPackageName());
             }
-            if (packages.length == 0) {
+            LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicySetByAdmin(
+                    PolicyDefinition.LOCK_TASK,
+                    enforcingAdmin,
+                    caller.getUserId());
+            LockTaskPolicy policy;
+            if (currentPolicy == null) {
+                policy = new LockTaskPolicy(Set.of(packages));
+            } else {
+                policy = new LockTaskPolicy(currentPolicy);
+                policy.setPackages(Set.of(packages));
+            }
+            if (policy.getPackages().isEmpty()
+                    && policy.getFlags() == DevicePolicyManager.LOCK_TASK_FEATURE_NONE) {
                 mDevicePolicyEngine.removeLocalPolicy(
                         PolicyDefinition.LOCK_TASK,
                         enforcingAdmin,
                         caller.getUserId());
             } else {
-                LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicySetByAdmin(
-                        PolicyDefinition.LOCK_TASK,
-                        enforcingAdmin,
-                        caller.getUserId());
-                LockTaskPolicy policy;
-                if (currentPolicy == null) {
-                    policy = new LockTaskPolicy(Set.of(packages));
-                } else {
-                    policy = new LockTaskPolicy(currentPolicy);
-                    policy.setPackages(Set.of(packages));
-                }
-
                 mDevicePolicyEngine.setLocalPolicy(
                         PolicyDefinition.LOCK_TASK,
                         enforcingAdmin,
@@ -14876,18 +14883,26 @@
                     PolicyDefinition.LOCK_TASK,
                     enforcingAdmin,
                     caller.getUserId());
+            LockTaskPolicy policy;
             if (currentPolicy == null) {
-                throw new IllegalArgumentException("Can't set a lock task flags without setting "
-                        + "lock task packages first.");
+                policy = new LockTaskPolicy(flags);
+            } else {
+                policy = new LockTaskPolicy(currentPolicy);
+                policy.setFlags(flags);
             }
-            LockTaskPolicy policy = new LockTaskPolicy(currentPolicy);
-            policy.setFlags(flags);
-
-            mDevicePolicyEngine.setLocalPolicy(
-                    PolicyDefinition.LOCK_TASK,
-                    enforcingAdmin,
-                    policy,
-                    caller.getUserId());
+            if (policy.getPackages().isEmpty()
+                    && policy.getFlags() == DevicePolicyManager.LOCK_TASK_FEATURE_NONE) {
+                mDevicePolicyEngine.removeLocalPolicy(
+                        PolicyDefinition.LOCK_TASK,
+                        enforcingAdmin,
+                        caller.getUserId());
+            } else {
+                mDevicePolicyEngine.setLocalPolicy(
+                        PolicyDefinition.LOCK_TASK,
+                        enforcingAdmin,
+                        policy,
+                        caller.getUserId());
+            }
         } else {
             Objects.requireNonNull(who, "ComponentName is null");
             synchronized (getLockObject()) {
@@ -14945,18 +14960,34 @@
                     continue;
                 }
 
-                final List<String> lockTaskPackages = getUserData(userId).mLockTaskPackages;
-                // TODO(b/278438525): handle in the policy engine
-                if (!lockTaskPackages.isEmpty()) {
-                    Slogf.d(LOG_TAG,
-                            "User id " + userId + " not affiliated. Clearing lock task packages");
-                    setLockTaskPackagesLocked(userId, Collections.<String>emptyList());
-                }
-                final int lockTaskFeatures = getUserData(userId).mLockTaskFeatures;
-                if (lockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE){
-                    Slogf.d(LOG_TAG,
-                            "User id " + userId + " not affiliated. Clearing lock task features");
-                    setLockTaskFeaturesLocked(userId, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+                if (isPolicyEngineForFinanceFlagEnabled()) {
+                    Map<EnforcingAdmin, PolicyValue<LockTaskPolicy>> policies =
+                            mDevicePolicyEngine.getLocalPoliciesSetByAdmins(
+                                    PolicyDefinition.LOCK_TASK, userId);
+                    Set<EnforcingAdmin> admins = new HashSet<>(policies.keySet());
+                    for (EnforcingAdmin admin : admins) {
+                        if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
+                            mDevicePolicyEngine.removeLocalPolicy(
+                                    PolicyDefinition.LOCK_TASK, admin, userId);
+                        }
+                    }
+                } else {
+                    final List<String> lockTaskPackages = getUserData(userId).mLockTaskPackages;
+                    // TODO(b/278438525): handle in the policy engine
+                    if (!lockTaskPackages.isEmpty()) {
+                        Slogf.d(LOG_TAG,
+                                "User id " + userId
+                                        + " not affiliated. Clearing lock task packages");
+                        setLockTaskPackagesLocked(userId, Collections.<String>emptyList());
+                    }
+                    final int lockTaskFeatures = getUserData(userId).mLockTaskFeatures;
+                    if (lockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE) {
+                        Slogf.d(LOG_TAG,
+                                "User id " + userId
+                                        + " not affiliated. Clearing lock task features");
+                        setLockTaskFeaturesLocked(userId,
+                                DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+                    }
                 }
             }
         });
@@ -15454,12 +15485,12 @@
     public boolean setStatusBarDisabled(ComponentName who, String callerPackageName,
             boolean disabled) {
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             enforcePermission(MANAGE_DEVICE_POLICY_STATUS_BAR, caller.getPackageName(),
                     UserHandle.USER_ALL);
         } else {
@@ -15470,7 +15501,7 @@
 
         int userId = caller.getUserId();
         synchronized (getLockObject()) {
-            if (!isPermissionCheckFlagEnabled()) {
+            if (!isPolicyEngineForFinanceFlagEnabled()) {
                 Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId),
                         "Admin " + who + " is neither the device owner or affiliated "
                                 + "user's profile owner.");
@@ -15529,7 +15560,7 @@
     @Override
     public boolean isStatusBarDisabled(String callerPackage) {
         final CallerIdentity caller = getCallerIdentity(callerPackage);
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             enforceCanQuery(
                     MANAGE_DEVICE_POLICY_STATUS_BAR, caller.getPackageName(), caller.getUserId());
         } else {
@@ -15539,7 +15570,7 @@
 
         int userId = caller.getUserId();
         synchronized (getLockObject()) {
-            if (!isPermissionCheckFlagEnabled()) {
+            if (!isPolicyEngineForFinanceFlagEnabled()) {
                 Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId),
                         "Admin " + callerPackage
                                 + " is neither the device owner or affiliated user's profile owner.");
@@ -16191,17 +16222,13 @@
                         deviceOwner.second);
                 return result;
             }
-        } else if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)
-                || DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) {
+        } else if (DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) {
             synchronized (getLockObject()) {
                 final DevicePolicyData policy = getUserData(userId);
                 final int N = policy.mAdminList.size();
                 for (int i = 0; i < N; i++) {
                     final ActiveAdmin admin = policy.mAdminList.get(i);
-                    if ((admin.disableCamera &&
-                            DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction))
-                            || (admin.disableScreenCapture && DevicePolicyManager
-                            .POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction))) {
+                    if (admin.disableScreenCapture) {
                         result = new Bundle();
                         result.putInt(Intent.EXTRA_USER_ID, userId);
                         result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
@@ -16209,17 +16236,44 @@
                         return result;
                     }
                 }
-                // For the camera, a device owner on a different user can disable it globally,
-                // so we need an additional check.
-                if (result == null
-                        && DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) {
-                    final ActiveAdmin admin = getDeviceOwnerAdminLocked();
-                    if (admin != null && admin.disableCamera) {
-                        result = new Bundle();
-                        result.putInt(Intent.EXTRA_USER_ID, mOwners.getDeviceOwnerUserId());
-                        result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
-                                admin.info.getComponent());
-                        return result;
+            }
+        } else if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) {
+            if (isPolicyEngineForFinanceFlagEnabled()) {
+                PolicyDefinition<Boolean> policyDefinition =
+                        PolicyDefinition.getPolicyDefinitionForUserRestriction(
+                                UserManager.DISALLOW_CAMERA);
+                Boolean value = mDevicePolicyEngine.getResolvedPolicy(policyDefinition, userId);
+                if (value != null && value) {
+                    result = new Bundle();
+                    result.putInt(Intent.EXTRA_USER_ID, userId);
+                    return result;
+                }
+            } else {
+                synchronized (getLockObject()) {
+                    final DevicePolicyData policy = getUserData(userId);
+                    final int N = policy.mAdminList.size();
+                    for (int i = 0; i < N; i++) {
+                        final ActiveAdmin admin = policy.mAdminList.get(i);
+                        if (admin.disableCamera) {
+                            result = new Bundle();
+                            result.putInt(Intent.EXTRA_USER_ID, userId);
+                            result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+                                    admin.info.getComponent());
+                            return result;
+                        }
+                    }
+                    // For the camera, a device owner on a different user can disable it globally,
+                    // so we need an additional check.
+                    if (result == null
+                            && DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) {
+                        final ActiveAdmin admin = getDeviceOwnerAdminLocked();
+                        if (admin != null && admin.disableCamera) {
+                            result = new Bundle();
+                            result.putInt(Intent.EXTRA_USER_ID, mOwners.getDeviceOwnerUserId());
+                            result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+                                    admin.info.getComponent());
+                            return result;
+                        }
                     }
                 }
             }
@@ -16676,7 +16730,7 @@
             }
         }
         EnforcingAdmin enforcingAdmin;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
@@ -16847,7 +16901,7 @@
     public int getPermissionGrantState(ComponentName admin, String callerPackage,
             String packageName, String permission) throws RemoteException {
         final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             enforceCanQuery(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, caller.getPackageName(),
                     caller.getUserId());
         } else {
@@ -16895,6 +16949,7 @@
                     } else {
                         granted = PackageManager.PERMISSION_GRANTED;
                     }
+
                 }
             }
         }
@@ -18978,14 +19033,14 @@
             throw new IllegalArgumentException("token must be at least 32-byte long");
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(admin, callerPackageName);
         } else {
             caller = getCallerIdentity(admin);
         }
         final int userId = caller.getUserId();
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19041,7 +19096,7 @@
             return false;
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(admin, callerPackageName);
         } else {
             caller = getCallerIdentity(admin);
@@ -19049,7 +19104,7 @@
         final int userId = caller.getUserId();
         boolean result = false;
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19088,14 +19143,14 @@
             return false;
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(admin, callerPackageName);
         } else {
             caller = getCallerIdentity(admin);
         }
         int userId = caller.getUserId();
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19998,6 +20053,7 @@
         if (!mHasFeature) {
             return;
         }
+
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(packageNames, "Package names is null");
         final CallerIdentity caller = getCallerIdentity(who);
@@ -20014,9 +20070,12 @@
             saveSettingsLocked(caller.getUserId());
         }
         logSetCrossProfilePackages(who, packageNames);
-        final CrossProfileApps crossProfileApps = mContext.getSystemService(CrossProfileApps.class);
+        final CrossProfileApps crossProfileApps =
+                mContext.createContextAsUser(
+                        caller.getUserHandle(), /* flags= */ 0)
+                        .getSystemService(CrossProfileApps.class);
         mInjector.binderWithCleanCallingIdentity(
-                () -> crossProfileApps.resetInteractAcrossProfilesAppOps(
+        () -> crossProfileApps.resetInteractAcrossProfilesAppOps(
                         previousCrossProfilePackages, new HashSet<>(packageNames)));
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
index 0f6f3c5..20bd2d7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
@@ -42,10 +42,6 @@
     void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
             @NonNull LockTaskPolicy value) throws IOException {
         Objects.requireNonNull(value);
-        if (value.getPackages() == null || value.getPackages().isEmpty()) {
-            throw new IllegalArgumentException("Error saving LockTaskPolicy to file, lock task "
-                    + "packages must be present");
-        }
         serializer.attribute(
                 /* namespace= */ null,
                 ATTR_PACKAGES,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 12a8a75..289098e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -26,6 +26,7 @@
 import android.app.admin.PackagePolicyKey;
 import android.app.admin.PolicyKey;
 import android.app.admin.UserRestrictionPolicyKey;
+import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentFilter;
@@ -38,6 +39,7 @@
 import android.permission.AdminPermissionControlParams;
 import android.permission.PermissionControllerManager;
 import android.provider.Settings;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.server.LocalServices;
@@ -151,11 +153,15 @@
 
     static boolean setUserControlDisabledPackages(
             @Nullable Set<String> packages, int userId) {
-        Binder.withCleanCallingIdentity(() ->
-                LocalServices.getService(PackageManagerInternal.class)
-                        .setOwnerProtectedPackages(
-                                userId,
-                                packages == null ? null : packages.stream().toList()));
+        Binder.withCleanCallingIdentity(() -> {
+            LocalServices.getService(PackageManagerInternal.class)
+                    .setOwnerProtectedPackages(
+                            userId,
+                            packages == null ? null : packages.stream().toList());
+            LocalServices.getService(UsageStatsManagerInternal.class)
+                            .setAdminProtectedPackages(
+                            packages == null ? null : new ArraySet(packages), userId);
+        });
         return true;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
index 16406bc..4167c7e 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
@@ -23,8 +23,9 @@
 
 import android.app.ActivityManagerInternal;
 import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
 
-import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
@@ -39,13 +40,14 @@
  * Build/Install/Run:
  *  atest FrameworksServicesTests:ActivityManagerInternalTest
  */
+@Presubmit
+@SmallTest
 public class ActivityManagerInternalTest {
     private static final int TEST_UID1 = 111;
     private static final int TEST_UID2 = 112;
 
     private static final long TEST_PROC_STATE_SEQ1 = 1111;
     private static final long TEST_PROC_STATE_SEQ2 = 1112;
-    private static final long TEST_PROC_STATE_SEQ3 = 1113;
 
     @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
 
@@ -68,7 +70,6 @@
         mAmi = mAms.new LocalService();
     }
 
-    @MediumTest
     @Test
     public void testNotifyNetworkPolicyRulesUpdated() throws Exception {
         // Check there is no crash when there are no active uid records.
@@ -89,14 +90,6 @@
                 TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq
                 TEST_PROC_STATE_SEQ1, // procStateSeq to notify
                 false); // expectNotify
-
-        // Notify that network policy rules are updated for TEST_UID1 with procStateSeq older
-        // than it's UidRecord.curProcStateSeq and verify that there is no notify call.
-        verifyNetworkUpdatedProcStateSeq(
-                TEST_PROC_STATE_SEQ3, // curProcStateSeq
-                TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq
-                TEST_PROC_STATE_SEQ2, // procStateSeq to notify
-                false); // expectNotify
     }
 
     private void verifyNetworkUpdatedProcStateSeq(long curProcStateSeq,
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index d85db64b..34b88b0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1511,6 +1511,7 @@
      * Validates that when the device owner is removed, the reset password token is cleared
      */
     @Test
+    @Ignore("b/277916462")
     public void testClearDeviceOwner_clearResetPasswordToken() throws Exception {
         mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
@@ -2601,6 +2602,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetApplicationHiddenWithDO() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -2626,6 +2628,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetApplicationHiddenWithPOOfOrganizationOwnedDevice() throws Exception {
         final int MANAGED_PROFILE_USER_ID = CALLER_USER_HANDLE;
         final int MANAGED_PROFILE_ADMIN_UID =
@@ -4372,6 +4375,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledModifiesSetting() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -4383,6 +4387,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledWithPOOnUser0() throws Exception {
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         setupProfileOwnerOnUser0();
@@ -4394,6 +4399,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledFailWithPONotOnUser0() throws Exception {
         setupProfileOwner();
         assertExpectException(SecurityException.class, null,
@@ -4403,6 +4409,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledWithPOOfOrganizationOwnedDevice() throws Exception {
         setupProfileOwner();
         configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
@@ -5376,6 +5383,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testResetPasswordWithToken() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -5410,6 +5418,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void resetPasswordWithToken_NumericPin() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -5430,6 +5439,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void resetPasswordWithToken_EmptyPassword() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -7250,6 +7260,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testCanProfileOwnerResetPasswordWhenLocked() throws Exception {
         setDeviceEncryptionPerUser();
         setupProfileOwner();
@@ -7313,6 +7324,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAccountTypesWithManagementDisabledOnManagedProfile() throws Exception {
         setupProfileOwner();
 
@@ -7332,6 +7344,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAccountTypesWithManagementDisabledOnOrgOwnedManagedProfile()
             throws Exception {
         mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS);
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index ff89be7..5ea3029 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -17,6 +17,8 @@
 package com.android.server.display;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
@@ -26,6 +28,7 @@
 
 import android.app.PropertyInvalidatedCache;
 import android.graphics.Point;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.Surface;
@@ -47,6 +50,7 @@
     private static final int LAYER_STACK = 0;
     private static final int DISPLAY_WIDTH = 100;
     private static final int DISPLAY_HEIGHT = 200;
+    private static final int MODE_ID = 1;
 
     private LogicalDisplay mLogicalDisplay;
     private DisplayDevice mDisplayDevice;
@@ -65,6 +69,9 @@
         mDisplayDeviceInfo.height = DISPLAY_HEIGHT;
         mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
         mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
+        mDisplayDeviceInfo.modeId = MODE_ID;
+        mDisplayDeviceInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, /* refreshRate= */ 60)};
         when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(mDisplayDeviceInfo);
 
         // Disable binder caches in this process.
@@ -168,14 +175,34 @@
     }
 
     @Test
-    public void testLayoutLimitedRefreshRateNotClearedAfterUpdate() {
-        SurfaceControl.RefreshRateRange refreshRateRange = new SurfaceControl.RefreshRateRange(1,
-                2);
-        mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(refreshRateRange);
-        mLogicalDisplay.updateDisplayGroupIdLocked(1);
+    public void testUpdateLayoutLimitedRefreshRate() {
+        SurfaceControl.RefreshRateRange layoutLimitedRefreshRate =
+                new SurfaceControl.RefreshRateRange(0, 120);
+        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
+        mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(layoutLimitedRefreshRate);
+        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
+        // Display info should only be updated when updateLocked is called
+        assertEquals(info2, info1);
 
-        DisplayInfo result = mLogicalDisplay.getDisplayInfoLocked();
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
+        assertNotEquals(info3, info2);
+        assertEquals(layoutLimitedRefreshRate, info3.layoutLimitedRefreshRate);
+    }
 
-        assertEquals(refreshRateRange, result.layoutLimitedRefreshRate);
+    @Test
+    public void testUpdateRefreshRateThermalThrottling() {
+        SparseArray<SurfaceControl.RefreshRateRange> refreshRanges = new SparseArray<>();
+        refreshRanges.put(0, new SurfaceControl.RefreshRateRange(0, 120));
+        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
+        mLogicalDisplay.updateThermalRefreshRateThrottling(refreshRanges);
+        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
+        // Display info should only be updated when updateLocked is called
+        assertEquals(info2, info1);
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
+        assertNotEquals(info3, info2);
+        assertTrue(refreshRanges.contentEquals(info3.thermalRefreshRateThrottling));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 42c1fd9..e492252 100644
--- a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -27,7 +27,7 @@
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
 
-import static com.android.server.display.mode.DisplayModeDirector.Vote.INVALID_SIZE;
+import static com.android.server.display.mode.Vote.INVALID_SIZE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -94,7 +94,6 @@
 import com.android.server.display.TestUtils;
 import com.android.server.display.mode.DisplayModeDirector.BrightnessObserver;
 import com.android.server.display.mode.DisplayModeDirector.DesiredDisplayModeSpecs;
-import com.android.server.display.mode.DisplayModeDirector.Vote;
 import com.android.server.sensors.SensorManagerInternal;
 import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -127,7 +126,8 @@
     private static final String TAG = "DisplayModeDirectorTest";
     private static final boolean DEBUG = false;
     private static final float FLOAT_TOLERANCE = 0.01f;
-    private static final int DISPLAY_ID = 0;
+    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+    private static final int MODE_ID = 1;
     private static final float TRANSITION_POINT = 0.763f;
 
     private static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
@@ -223,8 +223,7 @@
         assertThat(modeSpecs.appRequest.render.min).isEqualTo(0f);
         assertThat(modeSpecs.appRequest.render.max).isEqualTo(Float.POSITIVE_INFINITY);
 
-        int numPriorities =
-                DisplayModeDirector.Vote.MAX_PRIORITY - DisplayModeDirector.Vote.MIN_PRIORITY + 1;
+        int numPriorities =  Vote.MAX_PRIORITY - Vote.MIN_PRIORITY + 1;
 
         // Ensure vote priority works as expected. As we add new votes with higher priority, they
         // should take precedence over lower priority votes.
@@ -2644,6 +2643,53 @@
         assertNull(vote);
     }
 
+    @Test
+    public void testUpdateLayoutLimitedRefreshRate_validDisplayInfo() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
+        float refreshRate = 60;
+        mInjector.mDisplayInfo.layoutLimitedRefreshRate =
+                new RefreshRateRange(refreshRate, refreshRate);
+        displayListener.onDisplayChanged(DISPLAY_ID);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
+        assertVoteForPhysicalRefreshRate(vote, /* refreshRate= */ refreshRate);
+
+        mInjector.mDisplayInfo.layoutLimitedRefreshRate = null;
+        displayListener.onDisplayChanged(DISPLAY_ID);
+
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
+        assertNull(vote);
+    }
+
+    @Test
+    public void testUpdateLayoutLimitedRefreshRate_invalidDisplayInfo() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
+        mInjector.mDisplayInfo.layoutLimitedRefreshRate = new RefreshRateRange(10, 10);
+        mInjector.mDisplayInfoValid = false;
+        displayListener.onDisplayChanged(DISPLAY_ID);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
+        assertNull(vote);
+    }
+
     private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
         return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
     }
@@ -2850,12 +2896,20 @@
 
     public static class FakesInjector implements DisplayModeDirector.Injector {
         private final FakeDeviceConfig mDeviceConfig;
+        private final DisplayInfo mDisplayInfo;
+        private final Display mDisplay;
+        private boolean mDisplayInfoValid = true;
         private ContentObserver mBrightnessObserver;
         private ContentObserver mSmoothDisplaySettingObserver;
         private ContentObserver mForcePeakRefreshRateSettingObserver;
 
         FakesInjector() {
             mDeviceConfig = new FakeDeviceConfig();
+            mDisplayInfo = new DisplayInfo();
+            mDisplayInfo.defaultModeId = MODE_ID;
+            mDisplayInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
+                    800, 600, /* refreshRate= */ 60)};
+            mDisplay = createDisplay(DISPLAY_ID);
         }
 
         @NonNull
@@ -2876,16 +2930,25 @@
         }
 
         @Override
+        public void registerDisplayListener(DisplayListener listener, Handler handler) {}
+
+        @Override
         public void registerDisplayListener(DisplayListener listener, Handler handler, long flag) {}
 
         @Override
+        public Display getDisplay(int displayId) {
+            return mDisplay;
+        }
+
+        @Override
         public Display[] getDisplays() {
-            return new Display[] { createDisplay(DISPLAY_ID) };
+            return new Display[] { mDisplay };
         }
 
         @Override
         public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) {
-            return false;
+            displayInfo.copyFrom(mDisplayInfo);
+            return mDisplayInfoValid;
         }
 
         @Override
@@ -2909,7 +2972,7 @@
         }
 
         protected Display createDisplay(int id) {
-            return new Display(DisplayManagerGlobal.getInstance(), id, new DisplayInfo(),
+            return new Display(DisplayManagerGlobal.getInstance(), id, mDisplayInfo,
                     ApplicationProvider.getApplicationContext().getResources());
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java b/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
index fd1889c..9ab6ee5 100644
--- a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
@@ -18,7 +18,6 @@
 
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
 import android.hardware.display.DisplayManager;
@@ -57,7 +56,7 @@
     private RegisteringFakesInjector mInjector = new RegisteringFakesInjector();
 
     private final TestHandler mHandler = new TestHandler(null);
-    private final FakeVoteStorage mStorage = new FakeVoteStorage();
+    private final VotesStorage mStorage = new VotesStorage(() -> {});
 
     @Before
     public void setUp() {
@@ -92,28 +91,26 @@
     public void testNotifyWithDefaultVotesForCritical() {
         // GIVEN 2 displays with no thermalThrottling config
         mObserver.observe();
-        assertEquals(0, mStorage.mVoteRegistry.size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
 
         // WHEN thermal sensor notifies CRITICAL
         mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_CRITICAL));
         mHandler.flush();
 
         // THEN 2 votes are added to storage with (0,60) render refresh rate(default behaviour)
-        assertEquals(2, mStorage.mVoteRegistry.size());
-
-        SparseArray<DisplayModeDirector.Vote> displayVotes = mStorage.mVoteRegistry.get(DISPLAY_ID);
+        SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID);
         assertEquals(1, displayVotes.size());
 
-        DisplayModeDirector.Vote vote = displayVotes.get(
-                DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE);
+        Vote vote = displayVotes.get(
+                Vote.PRIORITY_SKIN_TEMPERATURE);
         assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE);
         assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE);
 
-        SparseArray<DisplayModeDirector.Vote> otherDisplayVotes = mStorage.mVoteRegistry.get(
-                DISPLAY_ID_OTHER);
+        SparseArray<Vote> otherDisplayVotes = mStorage.getVotes(DISPLAY_ID_OTHER);
         assertEquals(1, otherDisplayVotes.size());
 
-        vote = otherDisplayVotes.get(DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE);
+        vote = otherDisplayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE);
         assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE);
         assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE);
     }
@@ -122,25 +119,29 @@
     public void testNotifyWithDefaultVotesChangeFromCriticalToSevere() {
         // GIVEN 2 displays with no thermalThrottling config AND temperature level CRITICAL
         mObserver.observe();
-        assertEquals(0, mStorage.mVoteRegistry.size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
         mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_CRITICAL));
         // WHEN thermal sensor notifies SEVERE
         mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE));
         mHandler.flush();
         // THEN all votes with PRIORITY_SKIN_TEMPERATURE are removed from the storage
-        assertEquals(0, mStorage.mVoteRegistry.size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
     }
 
     @Test
     public void testNotifyWithDefaultVotesForSevere() {
         // GIVEN 2 displays with no thermalThrottling config
         mObserver.observe();
-        assertEquals(0, mStorage.mVoteRegistry.size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
         // WHEN thermal sensor notifies CRITICAL
         mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE));
         mHandler.flush();
         // THEN nothing is added to the storage
-        assertEquals(0, mStorage.mVoteRegistry.size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
     }
 
     @Test
@@ -155,18 +156,20 @@
         mObserver = new SkinThermalStatusObserver(mInjector, mStorage, mHandler);
         mObserver.observe();
         mObserver.onDisplayChanged(DISPLAY_ID);
-        assertEquals(0, mStorage.mVoteRegistry.size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
         // WHEN thermal sensor notifies temperature above configured
         mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE));
         mHandler.flush();
         // THEN vote with refreshRate from config is added to the storage
-        assertEquals(1, mStorage.mVoteRegistry.size());
-        SparseArray<DisplayModeDirector.Vote> displayVotes = mStorage.mVoteRegistry.get(DISPLAY_ID);
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
+
+        SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID);
         assertEquals(1, displayVotes.size());
-        DisplayModeDirector.Vote vote = displayVotes.get(
-                DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE);
+        Vote vote = displayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE);
         assertEquals(90, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE);
         assertEquals(120, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE);
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size());
     }
 
     @Test
@@ -178,14 +181,13 @@
         mObserver.onDisplayAdded(DISPLAY_ID_ADDED);
         mHandler.flush();
         // THEN 3rd vote is added to storage with (0,60) render refresh rate(default behaviour)
-        assertEquals(3, mStorage.mVoteRegistry.size());
+        assertEquals(1, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(1, mStorage.getVotes(DISPLAY_ID_OTHER).size());
+        assertEquals(1, mStorage.getVotes(DISPLAY_ID_ADDED).size());
 
-        SparseArray<DisplayModeDirector.Vote> displayVotes = mStorage.mVoteRegistry.get(
-                DISPLAY_ID_ADDED);
-        assertEquals(1, displayVotes.size());
+        SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID_ADDED);
 
-        DisplayModeDirector.Vote vote = displayVotes.get(
-                DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE);
+        Vote vote = displayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE);
         assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE);
         assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE);
     }
@@ -200,9 +202,9 @@
         mObserver.onDisplayRemoved(DISPLAY_ID_ADDED);
         mHandler.flush();
         // THEN there are 2 votes in registry
-        assertEquals(2, mStorage.mVoteRegistry.size());
-        assertNotNull(mStorage.mVoteRegistry.get(DISPLAY_ID));
-        assertNotNull(mStorage.mVoteRegistry.get(DISPLAY_ID_OTHER));
+        assertEquals(1, mStorage.getVotes(DISPLAY_ID).size());
+        assertEquals(1, mStorage.getVotes(DISPLAY_ID_OTHER).size());
+        assertEquals(0, mStorage.getVotes(DISPLAY_ID_ADDED).size());
     }
 
     private static Temperature createTemperature(@Temperature.ThrottlingStatus int status) {
@@ -253,33 +255,10 @@
         public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) {
             SparseArray<SurfaceControl.RefreshRateRange> config = mOverriddenConfig.get(displayId);
             if (config != null) {
-                displayInfo.refreshRateThermalThrottling = config;
+                displayInfo.thermalRefreshRateThrottling = config;
                 return true;
             }
             return false;
         }
     }
-
-
-    private static class FakeVoteStorage implements DisplayModeDirector.BallotBox {
-        private final SparseArray<SparseArray<DisplayModeDirector.Vote>> mVoteRegistry =
-                new SparseArray<>();
-
-        @Override
-        public void vote(int displayId, int priority, DisplayModeDirector.Vote vote) {
-            SparseArray<DisplayModeDirector.Vote> votesPerDisplay = mVoteRegistry.get(displayId);
-            if (votesPerDisplay == null) {
-                votesPerDisplay = new SparseArray<>();
-                mVoteRegistry.put(displayId, votesPerDisplay);
-            }
-            if (vote == null) {
-                votesPerDisplay.remove(priority);
-            } else {
-                votesPerDisplay.put(priority, vote);
-            }
-            if (votesPerDisplay.size() == 0) {
-                mVoteRegistry.remove(displayId);
-            }
-        }
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java
new file mode 100644
index 0000000..287fdd5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.util.SparseArray;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VotesStorageTest {
+    private static final int DISPLAY_ID = 100;
+    private static final int PRIORITY = Vote.PRIORITY_APP_REQUEST_SIZE;
+    private static final Vote VOTE = Vote.forDisableRefreshRateSwitching();
+    private static final int DISPLAY_ID_OTHER = 101;
+    private static final int PRIORITY_OTHER = Vote.PRIORITY_FLICKER_REFRESH_RATE;
+    private static final Vote VOTE_OTHER = Vote.forBaseModeRefreshRate(10f);
+
+    @Mock
+    public VotesStorage.Listener mVotesListener;
+
+    private VotesStorage mVotesStorage;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mVotesStorage = new VotesStorage(mVotesListener);
+    }
+
+    @Test
+    public void addsVoteToStorage() {
+        // WHEN updateVote is called
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+        // THEN vote is added to the storage
+        SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID);
+        assertThat(votes.size()).isEqualTo(1);
+        assertThat(votes.get(PRIORITY)).isEqualTo(VOTE);
+        assertThat(mVotesStorage.getVotes(DISPLAY_ID_OTHER).size()).isEqualTo(0);
+    }
+
+    @Test
+    public void notifiesVoteListenerIfVoteAdded() {
+        // WHEN updateVote is called
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+        // THEN listener is notified
+        verify(mVotesListener).onChanged();
+    }
+
+    @Test
+    public void addsAnotherVoteToStorageWithDifferentPriority() {
+        // GIVEN vote storage with one vote
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+        // WHEN updateVote is called with other priority
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE_OTHER);
+        // THEN another vote is added to storage
+        SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID);
+        assertThat(votes.size()).isEqualTo(2);
+        assertThat(votes.get(PRIORITY)).isEqualTo(VOTE);
+        assertThat(votes.get(PRIORITY_OTHER)).isEqualTo(VOTE_OTHER);
+        assertThat(mVotesStorage.getVotes(DISPLAY_ID_OTHER).size()).isEqualTo(0);
+    }
+
+    @Test
+    public void replacesVoteInStorageForSamePriority() {
+        // GIVEN vote storage with one vote
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+        // WHEN updateVote is called with same priority
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE_OTHER);
+        // THEN vote is replaced by other vote
+        SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID);
+        assertThat(votes.size()).isEqualTo(1);
+        assertThat(votes.get(PRIORITY)).isEqualTo(VOTE_OTHER);
+        assertThat(mVotesStorage.getVotes(DISPLAY_ID_OTHER).size()).isEqualTo(0);
+    }
+
+    @Test
+    public void removesVoteInStorageForSamePriority() {
+        // GIVEN vote storage with one vote
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+        // WHEN update is called with same priority and null vote
+        mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, null);
+        // THEN vote removed from the storage
+        assertThat(mVotesStorage.getVotes(DISPLAY_ID).size()).isEqualTo(0);
+        assertThat(mVotesStorage.getVotes(DISPLAY_ID_OTHER).size()).isEqualTo(0);
+    }
+
+    @Test
+    public void addsGlobalDisplayVoteToStorage() {
+        // WHEN updateGlobalVote is called
+        mVotesStorage.updateGlobalVote(PRIORITY, VOTE);
+        // THEN it is added to the storage for every display
+        SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID);
+        assertThat(votes.size()).isEqualTo(1);
+        assertThat(votes.get(PRIORITY)).isEqualTo(VOTE);
+        votes = mVotesStorage.getVotes(DISPLAY_ID_OTHER);
+        assertThat(votes.size()).isEqualTo(1);
+        assertThat(votes.get(PRIORITY)).isEqualTo(VOTE);
+    }
+
+    @Test
+    public void ignoresVotesWithLowerThanMinPriority() {
+        // WHEN updateVote is called with invalid (lower than min) priority
+        mVotesStorage.updateVote(DISPLAY_ID, Vote.MIN_PRIORITY - 1, VOTE);
+        // THEN vote is not added to the storage AND listener not notified
+        assertThat(mVotesStorage.getVotes(DISPLAY_ID).size()).isEqualTo(0);
+        verify(mVotesListener, never()).onChanged();
+    }
+
+    @Test
+    public void ignoresVotesWithGreaterThanMaxPriority() {
+        // WHEN updateVote is called with invalid (greater than max) priority
+        mVotesStorage.updateVote(DISPLAY_ID, Vote.MAX_PRIORITY + 1, VOTE);
+        // THEN vote is not added to the storage AND listener not notified
+        assertThat(mVotesStorage.getVotes(DISPLAY_ID).size()).isEqualTo(0);
+        verify(mVotesListener, never()).onChanged();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
index 0f4d4e8..64c05dc 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -250,20 +250,24 @@
         `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
         `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
 
-        dataStore.setKeyboardBacklightBrightness(
-            keyboardWithBacklight.descriptor,
-            LIGHT_ID,
-            MAX_BRIGHTNESS
-        )
+        for (level in 1 until BRIGHTNESS_VALUE_FOR_LEVEL.size) {
+            dataStore.setKeyboardBacklightBrightness(
+                    keyboardWithBacklight.descriptor,
+                    LIGHT_ID,
+                    BRIGHTNESS_VALUE_FOR_LEVEL[level] - 1
+            )
 
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-        keyboardBacklightController.notifyUserActivity()
-        testLooper.dispatchNext()
-        assertEquals(
-            "Keyboard backlight level should be restored to the level saved in the data store",
-            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
-            lightColorMap[LIGHT_ID]
-        )
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            keyboardBacklightController.notifyUserActivity()
+            testLooper.dispatchNext()
+            assertEquals(
+                    "Keyboard backlight level should be restored to the level saved in the data " +
+                            "store",
+                    Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
+                    lightColorMap[LIGHT_ID]
+            )
+            keyboardBacklightController.onInputDeviceRemoved(DEVICE_ID)
+        }
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index ea3f3bc..d0d28c3 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -633,6 +633,30 @@
                 0,
                 keyboardLayouts.size
             )
+
+            // If IME doesn't have a corresponding language tag, then should show all available
+            // layouts no matter the script code.
+            keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo, null
+                )
+            assertNotEquals(
+                "New UI: getKeyboardLayoutListForInputDevice API should return all layouts if" +
+                    "language tag or subtype not provided",
+                0,
+                keyboardLayouts.size
+            )
+            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Latin " +
+                "layouts if language tag or subtype not provided",
+                containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Cyrillic " +
+                "layouts if language tag or subtype not provided",
+                containsLayout(
+                    keyboardLayouts,
+                    createLayoutDescriptor("keyboard_layout_russian")
+                )
+            )
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index b7f90d4..c04df30 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -18,6 +18,12 @@
 
 import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
 import static android.Manifest.permission.NETWORK_STACK;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
+import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
 import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
@@ -396,6 +402,26 @@
         }
     }
 
+    // TODO: Use TestLooperManager instead.
+    /**
+     * Helper that leverages try-with-resources to pause dispatch of
+     * {@link #mHandlerThread} until released.
+     */
+    static class SyncBarrier implements AutoCloseable {
+        private final int mToken;
+        private Handler mHandler;
+
+        SyncBarrier(Handler handler) {
+            mHandler = handler;
+            mToken = mHandler.getLooper().getQueue().postSyncBarrier();
+        }
+
+        @Override
+        public void close() throws Exception {
+            mHandler.getLooper().getQueue().removeSyncBarrier(mToken);
+        }
+    }
+
     @Before
     public void callSystemReady() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -1018,25 +1044,113 @@
     // don't check for side-effects (like calls to NetworkManagementService) neither cover all
     // different modes (Data Saver, Battery Saver, Doze, App idle, etc...).
     // These scenarios are extensively tested on CTS' HostsideRestrictBackgroundNetworkTests.
+    @SuppressWarnings("GuardedBy")
     @Test
     public void testUidForeground() throws Exception {
         // push all uids into background
         long procStateSeq = 0;
-        callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE, procStateSeq++);
-        callOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_SERVICE, procStateSeq++);
-        assertFalse(mService.isUidForeground(UID_A));
-        assertFalse(mService.isUidForeground(UID_B));
+        callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_SERVICE, procStateSeq++);
+        callAndWaitOnUidStateChanged(UID_B, PROCESS_STATE_SERVICE, procStateSeq++);
+        assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_A));
+        assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_A));
+        assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_B));
+        assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_B));
 
         // push one of the uids into foreground
-        callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_TOP, procStateSeq++);
-        assertTrue(mService.isUidForeground(UID_A));
-        assertFalse(mService.isUidForeground(UID_B));
+        callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_TOP, procStateSeq++);
+        assertTrue(mService.isUidForegroundOnRestrictPowerUL(UID_A));
+        assertTrue(mService.isUidForegroundOnRestrictBackgroundUL(UID_A));
+        assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_B));
+        assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_B));
 
         // and swap another uid into foreground
-        callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE, procStateSeq++);
-        callOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_TOP, procStateSeq++);
-        assertFalse(mService.isUidForeground(UID_A));
-        assertTrue(mService.isUidForeground(UID_B));
+        callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_SERVICE, procStateSeq++);
+        callAndWaitOnUidStateChanged(UID_B, PROCESS_STATE_TOP, procStateSeq++);
+        assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_A));
+        assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_A));
+        assertTrue(mService.isUidForegroundOnRestrictPowerUL(UID_B));
+        assertTrue(mService.isUidForegroundOnRestrictBackgroundUL(UID_B));
+
+        // change capability of an uid to allow access to power restricted network
+        callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_SERVICE, procStateSeq++,
+                PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK);
+        callAndWaitOnUidStateChanged(UID_B, PROCESS_STATE_SERVICE, procStateSeq++,
+                PROCESS_CAPABILITY_NONE);
+        assertTrue(mService.isUidForegroundOnRestrictPowerUL(UID_A));
+        assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_A));
+        assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_B));
+        assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_B));
+
+        // change capability of an uid to allow access to user restricted network
+        callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_IMPORTANT_FOREGROUND, procStateSeq++,
+                PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK);
+        callAndWaitOnUidStateChanged(UID_B, PROCESS_STATE_SERVICE, procStateSeq++,
+                PROCESS_CAPABILITY_NONE);
+        assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_A));
+        assertTrue(mService.isUidForegroundOnRestrictBackgroundUL(UID_A));
+        assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_B));
+        assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_B));
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testUidForeground_withPendingState() throws Exception {
+        long procStateSeq = 0;
+        callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_SERVICE,
+                procStateSeq++, PROCESS_CAPABILITY_NONE);
+        callAndWaitOnUidStateChanged(UID_B, PROCESS_STATE_SERVICE,
+                procStateSeq++, PROCESS_CAPABILITY_NONE);
+        assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_A));
+        assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_A));
+        assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_B));
+        assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_B));
+
+        try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) {
+            // Verify that a callback with an old procStateSeq is ignored.
+            callOnUidStatechanged(UID_A, PROCESS_STATE_TOP, 0,
+                    PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK);
+            assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_A));
+            assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_A));
+            assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_B));
+            assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_B));
+
+            callOnUidStatechanged(UID_A, PROCESS_STATE_SERVICE, procStateSeq++,
+                    PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK);
+            assertTrue(mService.isUidForegroundOnRestrictPowerUL(UID_A));
+            assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_A));
+            assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_B));
+            assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_B));
+
+            callOnUidStatechanged(UID_A, PROCESS_STATE_IMPORTANT_FOREGROUND, procStateSeq++,
+                    PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK);
+            assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_A));
+            assertTrue(mService.isUidForegroundOnRestrictBackgroundUL(UID_A));
+            assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_B));
+            assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_B));
+
+            callOnUidStatechanged(UID_A, PROCESS_STATE_TOP, procStateSeq++,
+                    PROCESS_CAPABILITY_NONE);
+            assertTrue(mService.isUidForegroundOnRestrictPowerUL(UID_A));
+            assertTrue(mService.isUidForegroundOnRestrictBackgroundUL(UID_A));
+            assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_B));
+            assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_B));
+        }
+        waitForUidEventHandlerIdle();
+
+        assertTrue(mService.isUidForegroundOnRestrictPowerUL(UID_A));
+        assertTrue(mService.isUidForegroundOnRestrictBackgroundUL(UID_A));
+        assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_B));
+        assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_B));
+
+        try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) {
+            callOnUidStatechanged(UID_A, PROCESS_STATE_SERVICE, procStateSeq++,
+                    PROCESS_CAPABILITY_NONE);
+            assertTrue(mService.isUidForegroundOnRestrictPowerUL(UID_A));
+            assertTrue(mService.isUidForegroundOnRestrictBackgroundUL(UID_A));
+            assertFalse(mService.isUidForegroundOnRestrictPowerUL(UID_B));
+            assertFalse(mService.isUidForegroundOnRestrictBackgroundUL(UID_B));
+        }
+        waitForUidEventHandlerIdle();
     }
 
     @Test
@@ -1417,14 +1531,28 @@
     @Test
     public void testOnUidStateChanged_notifyAMS() throws Exception {
         final long procStateSeq = 222;
-        callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE, procStateSeq);
+        callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_SERVICE, procStateSeq);
         verify(mActivityManagerInternal).notifyNetworkPolicyRulesUpdated(UID_A, procStateSeq);
     }
 
-    private void callOnUidStateChanged(int uid, int procState, long procStateSeq)
+    private void callAndWaitOnUidStateChanged(int uid, int procState, long procStateSeq)
             throws Exception {
-        mUidObserver.onUidStateChanged(uid, procState, procStateSeq,
-                ActivityManager.PROCESS_CAPABILITY_NONE);
+        callAndWaitOnUidStateChanged(uid, procState, procStateSeq,
+                PROCESS_CAPABILITY_NONE);
+    }
+
+    private void callAndWaitOnUidStateChanged(int uid, int procState, long procStateSeq,
+            int capability) throws Exception {
+        callOnUidStatechanged(uid, procState, procStateSeq, capability);
+        waitForUidEventHandlerIdle();
+    }
+
+    private void callOnUidStatechanged(int uid, int procState, long procStateSeq, int capability)
+            throws Exception {
+        mUidObserver.onUidStateChanged(uid, procState, procStateSeq, capability);
+    }
+
+    private void waitForUidEventHandlerIdle() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         mService.mUidEventHandler.post(() -> {
             latch.countDown();
@@ -1927,9 +2055,9 @@
 
     @Test
     public void testLowPowerStandbyAllowlist() throws Exception {
-        callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_TOP, 0);
-        callOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
-        callOnUidStateChanged(UID_C, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
+        callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_TOP, 0);
+        callAndWaitOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
+        callAndWaitOnUidStateChanged(UID_C, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
         expectHasInternetPermission(UID_A, true);
         expectHasInternetPermission(UID_B, true);
         expectHasInternetPermission(UID_C, true);
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerEventTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerEventTest.java
new file mode 100644
index 0000000..1c89506
--- /dev/null
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerEventTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.soundtrigger.SoundTriggerEvent.ServiceEvent;
+import com.android.server.soundtrigger.SoundTriggerEvent.SessionEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.UUID;
+
+@RunWith(AndroidJUnit4.class)
+public final class SoundTriggerEventTest {
+    private static final ServiceEvent.Type serviceEventType = ServiceEvent.Type.ATTACH;
+    private static final SessionEvent.Type sessionEventType = SessionEvent.Type.DETACH;
+
+    @Test
+    public void serviceEventNoPackageNoError_getStringContainsType() {
+        final var event = new ServiceEvent(serviceEventType);
+        final var stringRep = event.eventToString();
+        assertThat(stringRep).contains(serviceEventType.name());
+        assertThat(stringRep).ignoringCase().doesNotContain("error");
+    }
+
+    @Test
+    public void serviceEventPackageNoError_getStringContainsTypeAndPackage() {
+        final var packageName = "com.android.package.name";
+        final var event = new ServiceEvent(serviceEventType, packageName);
+        final var stringRep = event.eventToString();
+        assertThat(stringRep).contains(serviceEventType.name());
+        assertThat(stringRep).contains(packageName);
+        assertThat(stringRep).ignoringCase().doesNotContain("error");
+    }
+
+    @Test
+    public void serviceEventPackageError_getStringContainsTypeAndPackageAndErrorAndMessage() {
+        final var packageName = "com.android.package.name";
+        final var errorString = "oh no an ERROR occurred";
+        final var event = new ServiceEvent(serviceEventType, packageName, errorString);
+        final var stringRep = event.eventToString();
+        assertThat(stringRep).contains(serviceEventType.name());
+        assertThat(stringRep).contains(packageName);
+        assertThat(stringRep).contains(errorString);
+        assertThat(stringRep).ignoringCase().contains("error");
+    }
+
+    @Test
+    public void sessionEventUUIDNoError_getStringContainsUUID() {
+        final var uuid = new UUID(5, -7);
+        final var event = new SessionEvent(sessionEventType, uuid);
+        final var stringRep = event.eventToString();
+        assertThat(stringRep).contains(sessionEventType.name());
+        assertThat(stringRep).contains(uuid.toString());
+        assertThat(stringRep).ignoringCase().doesNotContain("error");
+    }
+
+    @Test
+    public void sessionEventUUIDError_getStringContainsUUIDAndError() {
+        final var uuid = new UUID(5, -7);
+        final var errorString = "oh no an ERROR occurred";
+        final var event = new SessionEvent(sessionEventType, uuid, errorString);
+        final var stringRep = event.eventToString();
+        assertThat(stringRep).contains(sessionEventType.name());
+        assertThat(stringRep).contains(uuid.toString());
+        assertThat(stringRep).ignoringCase().contains("error");
+        assertThat(stringRep).contains(errorString);
+    }
+}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java
index 4d3c26f..eb117d1 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.soundtrigger_middleware;
 
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.ServiceEvent;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent;
 import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -55,6 +57,9 @@
 
 @RunWith(JUnit4.class)
 public class SoundTriggerMiddlewareLoggingTest {
+    private static final ServiceEvent.Type SERVICE_TYPE = ServiceEvent.Type.ATTACH;
+    private static final SessionEvent.Type SESSION_TYPE = SessionEvent.Type.LOAD_MODEL;
+
     private FakeLatencyTracker mLatencyTracker;
     @Mock
     private BatteryStatsInternal mBatteryStatsInternal;
@@ -184,4 +189,138 @@
         callback.onPhraseRecognition(0 /* modelHandle */, successEventWithKeyphraseId,
                 0 /* captureSession */);
     }
+
+    @Test
+    public void serviceEventException_getStringContainsInfo() {
+        String packageName = "com.android.test";
+        Exception exception = new Exception("test");
+        Object param1 = new Object();
+        Object param2 = new Object();
+        final var event = ServiceEvent.createForException(
+                SERVICE_TYPE, packageName, exception, param1, param2);
+        final var stringRep = event.eventToString();
+        assertThat(stringRep).contains(SERVICE_TYPE.name());
+        assertThat(stringRep).contains(packageName);
+        assertThat(stringRep).contains(exception.toString());
+        assertThat(stringRep).contains(param1.toString());
+        assertThat(stringRep).contains(param2.toString());
+        assertThat(stringRep).ignoringCase().contains("error");
+    }
+
+    @Test
+    public void serviceEventExceptionNoArgs_getStringContainsInfo() {
+        String packageName = "com.android.test";
+        Exception exception = new Exception("test");
+        final var event = ServiceEvent.createForException(
+                SERVICE_TYPE, packageName, exception);
+        final var stringRep = event.eventToString();
+        assertThat(stringRep).contains(SERVICE_TYPE.name());
+        assertThat(stringRep).contains(packageName);
+        assertThat(stringRep).contains(exception.toString());
+        assertThat(stringRep).ignoringCase().contains("error");
+    }
+
+    @Test
+    public void serviceEventReturn_getStringContainsInfo() {
+        String packageName = "com.android.test";
+        Object param1 = new Object();
+        Object param2 = new Object();
+        Object retValue = new Object();
+        final var event = ServiceEvent.createForReturn(
+                SERVICE_TYPE, packageName, retValue, param1, param2);
+        final var stringRep = event.eventToString();
+        assertThat(stringRep).contains(SERVICE_TYPE.name());
+        assertThat(stringRep).contains(packageName);
+        assertThat(stringRep).contains(retValue.toString());
+        assertThat(stringRep).contains(param1.toString());
+        assertThat(stringRep).contains(param2.toString());
+        assertThat(stringRep).ignoringCase().doesNotContain("error");
+    }
+
+    @Test
+    public void serviceEventReturnNoArgs_getStringContainsInfo() {
+        String packageName = "com.android.test";
+        Object retValue = new Object();
+        final var event = ServiceEvent.createForReturn(
+                SERVICE_TYPE, packageName, retValue);
+        final var stringRep = event.eventToString();
+        assertThat(stringRep).contains(SERVICE_TYPE.name());
+        assertThat(stringRep).contains(packageName);
+        assertThat(stringRep).contains(retValue.toString());
+        assertThat(stringRep).ignoringCase().doesNotContain("error");
+    }
+
+    @Test
+    public void sessionEventException_getStringContainsInfo() {
+        Object param1 = new Object();
+        Object param2 = new Object();
+        Exception exception = new Exception("test");
+        final var event = SessionEvent.createForException(
+                SESSION_TYPE, exception, param1, param2);
+        final var stringRep = event.eventToString();
+        assertThat(stringRep).contains(SESSION_TYPE.name());
+        assertThat(stringRep).contains(exception.toString());
+        assertThat(stringRep).contains(param1.toString());
+        assertThat(stringRep).contains(param2.toString());
+        assertThat(stringRep).ignoringCase().contains("error");
+    }
+
+    @Test
+    public void sessionEventExceptionNoArgs_getStringContainsInfo() {
+        Exception exception = new Exception("test");
+        final var event = SessionEvent.createForException(
+                SESSION_TYPE, exception);
+        final var stringRep = event.eventToString();
+        assertThat(stringRep).contains(SESSION_TYPE.name());
+        assertThat(stringRep).contains(exception.toString());
+        assertThat(stringRep).ignoringCase().contains("error");
+    }
+
+    @Test
+    public void sessionEventReturn_getStringContainsInfo() {
+        Object param1 = new Object();
+        Object param2 = new Object();
+        Object retValue = new Object();
+        final var event = SessionEvent.createForReturn(
+                SESSION_TYPE, retValue, param1, param2);
+        final var stringRep = event.eventToString();
+        assertThat(stringRep).contains(SESSION_TYPE.name());
+        assertThat(stringRep).contains(retValue.toString());
+        assertThat(stringRep).contains(param1.toString());
+        assertThat(stringRep).contains(param2.toString());
+        assertThat(stringRep).ignoringCase().doesNotContain("error");
+    }
+
+    @Test
+    public void sessionEventReturnNoArgs_getStringContainsInfo() {
+        Object retValue = new Object();
+        final var event = SessionEvent.createForReturn(
+                SESSION_TYPE, retValue);
+        final var stringRep = event.eventToString();
+        assertThat(stringRep).contains(SESSION_TYPE.name());
+        assertThat(stringRep).contains(retValue.toString());
+        assertThat(stringRep).ignoringCase().doesNotContain("error");
+    }
+
+    @Test
+    public void sessionEventVoid_getStringContainsInfo() {
+        Object param1 = new Object();
+        Object param2 = new Object();
+        final var event = SessionEvent.createForVoid(
+                SESSION_TYPE, param1, param2);
+        final var stringRep = event.eventToString();
+        assertThat(stringRep).contains(SESSION_TYPE.name());
+        assertThat(stringRep).contains(param1.toString());
+        assertThat(stringRep).contains(param2.toString());
+        assertThat(stringRep).ignoringCase().doesNotContain("error");
+    }
+
+    @Test
+    public void sessionEventVoidNoArgs_getStringContainsInfo() {
+        final var event = SessionEvent.createForVoid(
+                SESSION_TYPE);
+        final var stringRep = event.eventToString();
+        assertThat(stringRep).contains(SESSION_TYPE.name());
+        assertThat(stringRep).ignoringCase().doesNotContain("error");
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 8f0a5e6..bf6901e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -21,6 +21,8 @@
 import static android.view.KeyEvent.KEYCODE_C;
 import static android.view.KeyEvent.KEYCODE_CTRL_LEFT;
 import static android.view.KeyEvent.KEYCODE_E;
+import static android.view.KeyEvent.KEYCODE_ENTER;
+import static android.view.KeyEvent.KEYCODE_H;
 import static android.view.KeyEvent.KEYCODE_K;
 import static android.view.KeyEvent.KEYCODE_M;
 import static android.view.KeyEvent.KEYCODE_META_LEFT;
@@ -164,4 +166,24 @@
         sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ALT_LEFT}, 0);
         mPhoneWindowManager.assertToggleCapsLock();
     }
+
+    /**
+     * META + H to go to homescreen
+     */
+    @Test
+    public void testMetaH() {
+        mPhoneWindowManager.overrideLaunchHome();
+        sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_H}, 0);
+        mPhoneWindowManager.assertGoToHomescreen();
+    }
+
+    /**
+     * META + ENTER to go to homescreen
+     */
+    @Test
+    public void testMetaEnter() {
+        mPhoneWindowManager.overrideLaunchHome();
+        sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ENTER}, 0);
+        mPhoneWindowManager.assertGoToHomescreen();
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 6368f47..676bfb0 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -117,9 +117,9 @@
             throw new RuntimeException(e);
         }
 
-        for (KeyEvent event: events) {
+        for (int i = count - 1; i >= 0; i--) {
             final long eventTime = SystemClock.uptimeMillis();
-            final int keyCode = event.getKeyCode();
+            final int keyCode = keyCodes[i];
             final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode,
                     0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/,
                     InputDevice.SOURCE_KEYBOARD);
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index a2ee8a4..2665e19 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -353,6 +353,10 @@
                 () -> LocalServices.getService(eq(StatusBarManagerInternal.class)));
     }
 
+    void overrideLaunchHome() {
+        doNothing().when(mPhoneWindowManager).launchHomeFromHotKey(anyInt());
+    }
+
     /**
      * Below functions will check the policy behavior could be invoked.
      */
@@ -480,4 +484,9 @@
         transitionCaptor.getValue().onAppTransitionFinishedLocked(any());
         verify(mPhoneWindowManager).lockNow(null);
     }
+
+    void assertGoToHomescreen() {
+        waitForIdle();
+        verify(mPhoneWindowManager).launchHomeFromHotKey(anyInt());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 4b2d107..8e80485 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -541,6 +541,24 @@
                 SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_180));
     }
 
+    @Test
+    public void testFreezeRotation_reverseRotationDirectionAroundZAxis_yes() throws Exception {
+        mBuilder.build();
+        when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(true);
+
+        freezeRotation(Surface.ROTATION_90);
+        assertEquals(Surface.ROTATION_270, mTarget.getUserRotation());
+    }
+
+    @Test
+    public void testFreezeRotation_reverseRotationDirectionAroundZAxis_no() throws Exception {
+        mBuilder.build();
+        when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(false);
+
+        freezeRotation(Surface.ROTATION_90);
+        assertEquals(Surface.ROTATION_90, mTarget.getUserRotation());
+    }
+
     private boolean waitForUiHandler() {
         final CountDownLatch latch = new CountDownLatch(1);
         UiThread.getHandler().post(latch::countDown);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index fb4f2ee..1cec0ef 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -41,8 +41,9 @@
 
 import android.annotation.NonNull;
 import android.app.WindowConfiguration;
-import android.content.res.Configuration;
+import android.content.ContentResolver;
 import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.Surface;
@@ -439,6 +440,7 @@
     public void testDisplayWindowSettingsAppliedOnDisplayReady() {
         // Set forced densities for two displays in DisplayWindowSettings
         final DisplayContent dc = createMockSimulatedDisplay();
+        final ContentResolver contentResolver = useFakeSettingsProvider();
         mDisplayWindowSettings.setForcedDensity(mPrimaryDisplay.getDisplayInfo(), 123,
                 0 /* userId */);
         mDisplayWindowSettings.setForcedDensity(dc.getDisplayInfo(), 456, 0 /* userId */);
@@ -450,15 +452,21 @@
         assertFalse(mPrimaryDisplay.mWaitingForConfig);
         assertFalse(dc.mWaitingForConfig);
 
+        final int invalidW = Integer.MAX_VALUE;
+        final int invalidH = Integer.MAX_VALUE;
+        // Verify that applyForcedPropertiesForDefaultDisplay() handles invalid size request.
+        Settings.Global.putString(contentResolver, Settings.Global.DISPLAY_SIZE_FORCED,
+                invalidW + "," + invalidH);
         // Notify WM that the displays are ready and check that they are reconfigured.
         mWm.displayReady();
         waitUntilHandlersIdle();
 
-        final Configuration config = new Configuration();
-        mPrimaryDisplay.computeScreenConfiguration(config);
-        assertEquals(123, config.densityDpi);
-        dc.computeScreenConfiguration(config);
-        assertEquals(456, config.densityDpi);
+        // Density is set successfully.
+        assertEquals(123, mPrimaryDisplay.getConfiguration().densityDpi);
+        assertEquals(456, dc.getConfiguration().densityDpi);
+        // Invalid size won't be applied.
+        assertNotEquals(invalidW, mPrimaryDisplay.mBaseDisplayWidth);
+        assertNotEquals(invalidH, mPrimaryDisplay.mBaseDisplayHeight);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java
index 9db647a..89d7252 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.os.Build.HW_TIMEOUT_MULTIPLIER;
 import static android.window.SurfaceSyncGroup.TRANSACTION_READY_TIMEOUT;
 
 import static org.junit.Assert.assertEquals;
@@ -52,6 +53,8 @@
 public class SurfaceSyncGroupTests {
     private static final String TAG = "SurfaceSyncGroupTests";
 
+    private static final long TIMEOUT_S = HW_TIMEOUT_MULTIPLIER * 5L;
+
     @Rule
     public ActivityTestRule<SurfaceSyncGroupActivity> mActivityRule = new ActivityTestRule<>(
             SurfaceSyncGroupActivity.class);
@@ -93,7 +96,7 @@
         addSecondSyncGroup(secondSsg, secondDrawCompleteLatch, bothSyncGroupsComplete);
 
         assertTrue("Failed to draw two frames",
-                secondDrawCompleteLatch.await(5, TimeUnit.SECONDS));
+                secondDrawCompleteLatch.await(TIMEOUT_S, TimeUnit.SECONDS));
 
         mHandler.postDelayed(() -> {
             // Don't add a markSyncReady for the first sync group until after it's added to another
@@ -105,7 +108,7 @@
         }, 200);
 
         assertTrue("Failed to wait for both SurfaceSyncGroups to apply",
-                bothSyncGroupsComplete.await(5, TimeUnit.SECONDS));
+                bothSyncGroupsComplete.await(TIMEOUT_S, TimeUnit.SECONDS));
 
         validateScreenshot();
     }
@@ -123,7 +126,7 @@
                 transaction -> mHandler.postDelayed(() -> {
                     try {
                         assertTrue("Failed to draw two frames",
-                                secondDrawCompleteLatch.await(5, TimeUnit.SECONDS));
+                                secondDrawCompleteLatch.await(TIMEOUT_S, TimeUnit.SECONDS));
                     } catch (InterruptedException e) {
                         throw new RuntimeException(e);
                     }
@@ -143,7 +146,7 @@
         addSecondSyncGroup(secondSsg, secondDrawCompleteLatch, bothSyncGroupsComplete);
 
         assertTrue("Failed to wait for both SurfaceSyncGroups to apply",
-                bothSyncGroupsComplete.await(5, TimeUnit.SECONDS));
+                bothSyncGroupsComplete.await(TIMEOUT_S, TimeUnit.SECONDS));
 
         validateScreenshot();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 0dac346..b59f027 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -23,6 +23,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
@@ -36,6 +37,7 @@
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 import static android.window.TransitionInfo.isIndependent;
@@ -74,6 +76,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.view.SurfaceControl;
+import android.view.WindowManager;
 import android.window.IDisplayAreaOrganizer;
 import android.window.IRemoteTransition;
 import android.window.ITaskFragmentOrganizer;
@@ -96,6 +99,7 @@
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 import java.util.function.Function;
 
 /**
@@ -1903,7 +1907,7 @@
         assertTrue(targets.isEmpty());
 
         // After collecting order changes, it should recognize that a task moved to top.
-        transition.collectOrderChanges();
+        transition.collectOrderChanges(true);
         targets = Transition.calculateTargets(participants, changes);
         assertEquals(1, targets.size());
 
@@ -1914,6 +1918,192 @@
         assertEquals(TRANSIT_CHANGE, info.getChanges().get(0).getMode());
     }
 
+    private class OrderChangeTestSetup {
+        final TransitionController mController;
+        final TestTransitionPlayer mPlayer;
+        final Transition mTransitA;
+        final Transition mTransitB;
+
+        OrderChangeTestSetup() {
+            mController = mAtm.getTransitionController();
+            mPlayer = registerTestTransitionPlayer();
+            mController.setSyncEngine(mWm.mSyncEngine);
+
+            mTransitA = createTestTransition(TRANSIT_OPEN, mController);
+            mTransitA.mParallelCollectType = Transition.PARALLEL_TYPE_MUTUAL;
+            mTransitB = createTestTransition(TRANSIT_OPEN, mController);
+            mTransitB.mParallelCollectType = Transition.PARALLEL_TYPE_MUTUAL;
+        }
+
+        void startParallelCollect(boolean activityLevelFirst) {
+            // Start with taskB on top and taskA on bottom but both visible.
+            final Task taskA = createTask(mDisplayContent);
+            taskA.setVisibleRequested(true);
+            final ActivityRecord actA = createActivityRecord(taskA);
+            final TestWindowState winA = createWindowState(
+                    new WindowManager.LayoutParams(TYPE_BASE_APPLICATION), actA);
+            actA.addWindow(winA);
+            final ActivityRecord actB = createActivityRecord(taskA);
+            final TestWindowState winB = createWindowState(
+                    new WindowManager.LayoutParams(TYPE_BASE_APPLICATION), actB);
+            actB.addWindow(winB);
+
+            final Task taskB = createTask(mDisplayContent);
+            actA.setVisibleRequested(true);
+            actB.setVisibleRequested(false);
+            taskB.setVisibleRequested(true);
+            assertTrue(actA.isAttached());
+
+            final Consumer<Boolean> startAndCollectA = (doReady) -> {
+                mController.startCollectOrQueue(mTransitA, (deferred) -> {
+                });
+
+                // Collect activity-level change into A
+                mTransitA.collect(actA);
+                actA.setVisibleRequested(false);
+                winA.onSyncFinishedDrawing();
+                mTransitA.collect(actB);
+                actB.setVisibleRequested(true);
+                winB.onSyncFinishedDrawing();
+                mTransitA.start();
+                if (doReady) {
+                    mTransitA.setReady(mDisplayContent, true);
+                }
+            };
+            final Consumer<Boolean> startAndCollectB = (doReady) -> {
+                mController.startCollectOrQueue(mTransitB, (deferred) -> {
+                });
+                mTransitB.collect(taskA);
+                taskA.moveToFront("test");
+                mTransitB.start();
+                if (doReady) {
+                    mTransitB.setReady(mDisplayContent, true);
+                }
+            };
+
+            if (activityLevelFirst) {
+                startAndCollectA.accept(true);
+                startAndCollectB.accept(false);
+            } else {
+                startAndCollectB.accept(true);
+                startAndCollectA.accept(false);
+            }
+        }
+    }
+
+    @Test
+    public void testMoveToTopStartAfterReadyAfterParallel() {
+        // Start collect activity-only transit A
+        // Start collect task transit B in parallel
+        // finish A first -> should not include order change from B.
+        final OrderChangeTestSetup setup = new OrderChangeTestSetup();
+        setup.startParallelCollect(true /* activity first */);
+
+        mWm.mSyncEngine.tryFinishForTest(setup.mTransitA.getSyncId());
+        waitUntilHandlersIdle();
+        for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
+            assertNull(setup.mPlayer.mLastReady.getChanges().get(i).getTaskInfo());
+        }
+
+        setup.mTransitB.setAllReady();
+        mWm.mSyncEngine.tryFinishForTest(setup.mTransitB.getSyncId());
+        waitUntilHandlersIdle();
+        boolean hasOrderChange = false;
+        for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
+            final TransitionInfo.Change chg = setup.mPlayer.mLastReady.getChanges().get(i);
+            if (chg.getTaskInfo() == null) continue;
+            hasOrderChange = hasOrderChange || (chg.getFlags() & FLAG_MOVED_TO_TOP) != 0;
+        }
+        assertTrue(hasOrderChange);
+    }
+
+    @Test
+    public void testMoveToTopStartAfterReadyBeforeParallel() {
+        // Start collect activity-only transit A
+        // Start collect task transit B in parallel
+        // finish B first -> should include order change
+        // then finish A -> should NOT include order change.
+        final OrderChangeTestSetup setup = new OrderChangeTestSetup();
+        setup.startParallelCollect(true /* activity first */);
+        // Make it unready now so that it doesn't get dequeued automatically.
+        setup.mTransitA.setReady(mDisplayContent, false);
+
+        // Make task change ready first
+        setup.mTransitB.setAllReady();
+        mWm.mSyncEngine.tryFinishForTest(setup.mTransitB.getSyncId());
+        waitUntilHandlersIdle();
+        boolean hasOrderChange = false;
+        for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
+            final TransitionInfo.Change chg = setup.mPlayer.mLastReady.getChanges().get(i);
+            if (chg.getTaskInfo() == null) continue;
+            hasOrderChange = hasOrderChange || (chg.getFlags() & FLAG_MOVED_TO_TOP) != 0;
+        }
+        assertTrue(hasOrderChange);
+
+        setup.mTransitA.setAllReady();
+        mWm.mSyncEngine.tryFinishForTest(setup.mTransitA.getSyncId());
+        waitUntilHandlersIdle();
+        for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
+            assertNull(setup.mPlayer.mLastReady.getChanges().get(i).getTaskInfo());
+        }
+    }
+
+    @Test
+    public void testMoveToTopStartBeforeReadyAfterParallel() {
+        // Start collect task transit B
+        // Start collect activity-only transit A in parallel
+        // finish A first -> should not include order change from B.
+        final OrderChangeTestSetup setup = new OrderChangeTestSetup();
+        setup.startParallelCollect(false /* activity first */);
+        // Make B unready now so that it doesn't get dequeued automatically.
+        setup.mTransitB.setReady(mDisplayContent, false);
+
+        setup.mTransitA.setAllReady();
+        mWm.mSyncEngine.tryFinishForTest(setup.mTransitA.getSyncId());
+        waitUntilHandlersIdle();
+        for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
+            assertNull(setup.mPlayer.mLastReady.getChanges().get(i).getTaskInfo());
+        }
+
+        setup.mTransitB.setAllReady();
+        mWm.mSyncEngine.tryFinishForTest(setup.mTransitB.getSyncId());
+        waitUntilHandlersIdle();
+        boolean hasOrderChange = false;
+        for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
+            final TransitionInfo.Change chg = setup.mPlayer.mLastReady.getChanges().get(i);
+            if (chg.getTaskInfo() == null) continue;
+            hasOrderChange = hasOrderChange || (chg.getFlags() & FLAG_MOVED_TO_TOP) != 0;
+        }
+        assertTrue(hasOrderChange);
+    }
+
+    @Test
+    public void testMoveToTopStartBeforeReadyBeforeParallel() {
+        // Start collect task transit B
+        // Start collect activity-only transit A in parallel
+        // finish B first -> should include order change
+        // then finish A -> should NOT include order change.
+        final OrderChangeTestSetup setup = new OrderChangeTestSetup();
+        setup.startParallelCollect(false /* activity first */);
+
+        mWm.mSyncEngine.tryFinishForTest(setup.mTransitB.getSyncId());
+        waitUntilHandlersIdle();
+        boolean hasOrderChange = false;
+        for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
+            final TransitionInfo.Change chg = setup.mPlayer.mLastReady.getChanges().get(i);
+            if (chg.getTaskInfo() == null) continue;
+            hasOrderChange = hasOrderChange || (chg.getFlags() & FLAG_MOVED_TO_TOP) != 0;
+        }
+        assertTrue(hasOrderChange);
+
+        setup.mTransitA.setAllReady();
+        mWm.mSyncEngine.tryFinishForTest(setup.mTransitA.getSyncId());
+        waitUntilHandlersIdle();
+        for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
+            assertNull(setup.mPlayer.mLastReady.getChanges().get(i).getTaskInfo());
+        }
+    }
+
     @Test
     public void testQueueStartCollect() {
         final TransitionController controller = mAtm.getTransitionController();
@@ -2018,6 +2208,94 @@
         assertTrue(transitB.isCollecting());
     }
 
+    @Test
+    public void testQueueParallel() {
+        final TransitionController controller = mAtm.getTransitionController();
+        final TestTransitionPlayer player = registerTestTransitionPlayer();
+
+        mSyncEngine = createTestBLASTSyncEngine();
+        controller.setSyncEngine(mSyncEngine);
+
+        final Transition transitA = createTestTransition(TRANSIT_OPEN, controller);
+        transitA.mParallelCollectType = Transition.PARALLEL_TYPE_MUTUAL;
+        final Transition transitB = createTestTransition(TRANSIT_OPEN, controller);
+        transitB.mParallelCollectType = Transition.PARALLEL_TYPE_MUTUAL;
+        final Transition transitC = createTestTransition(TRANSIT_OPEN, controller);
+        transitC.mParallelCollectType = Transition.PARALLEL_TYPE_MUTUAL;
+        final Transition transitSync = createTestTransition(TRANSIT_OPEN, controller);
+        final Transition transitD = createTestTransition(TRANSIT_OPEN, controller);
+
+        controller.startCollectOrQueue(transitA, (deferred) -> {});
+        controller.startCollectOrQueue(transitB, (deferred) -> {});
+        controller.startCollectOrQueue(transitC, (deferred) -> {});
+        controller.startCollectOrQueue(transitSync, (deferred) -> {});
+        controller.startCollectOrQueue(transitD, (deferred) -> {});
+
+        assertTrue(transitA.isCollecting() && !transitA.isStarted());
+        // We still serialize on readiness
+        assertTrue(transitB.isPending());
+        assertTrue(transitC.isPending());
+
+        transitA.start();
+        transitA.setAllReady();
+        transitB.start();
+        transitB.setAllReady();
+
+        // A, B, and C should be collecting in parallel now.
+        assertTrue(transitA.isStarted());
+        assertTrue(transitB.isStarted());
+        assertTrue(transitC.isCollecting() && !transitC.isStarted());
+
+        transitC.start();
+        transitC.setAllReady();
+
+        assertTrue(transitA.isStarted());
+        assertTrue(transitB.isStarted());
+        assertTrue(transitC.isStarted());
+        // Not parallel so should remain pending
+        assertTrue(transitSync.isPending());
+        // After Sync, so should also remain pending.
+        assertTrue(transitD.isPending());
+        // There should always be a collector, since Sync can't collect yet, C should remain.
+        assertEquals(transitC, controller.getCollectingTransition());
+
+        mSyncEngine.tryFinishForTest(transitB.getSyncId());
+
+        // The other transitions should remain waiting.
+        assertTrue(transitA.isStarted());
+        assertTrue(transitB.isPlaying());
+        assertTrue(transitC.isStarted());
+        assertEquals(transitC, controller.getCollectingTransition());
+
+        mSyncEngine.tryFinishForTest(transitC.getSyncId());
+        assertTrue(transitA.isStarted());
+        assertTrue(transitC.isPlaying());
+        // The "collecting" one became ready, so the first "waiting" should move back to collecting.
+        assertEquals(transitA, controller.getCollectingTransition());
+
+        assertTrue(transitSync.isPending());
+        assertTrue(transitD.isPending());
+        mSyncEngine.tryFinishForTest(transitA.getSyncId());
+
+        // Now all collectors are done, so sync can be pulled-off the queue.
+        assertTrue(transitSync.isCollecting() && !transitSync.isStarted());
+        transitSync.start();
+        transitSync.setAllReady();
+        // Since D can run in parallel, it should be pulled-off the queue.
+        assertTrue(transitSync.isStarted());
+        assertTrue(transitD.isPending());
+
+        mSyncEngine.tryFinishForTest(transitSync.getSyncId());
+        assertTrue(transitD.isCollecting());
+
+        transitD.start();
+        transitD.setAllReady();
+        mSyncEngine.tryFinishForTest(transitD.getSyncId());
+
+        // Now nothing should be collecting
+        assertFalse(controller.isCollecting());
+    }
+
     private static void makeTaskOrganized(Task... tasks) {
         final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
         for (Task t : tasks) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 4530963..01ddcca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -382,7 +382,7 @@
         assertTrue(wallpaperWindow.isVisible());
         assertTrue(token.isVisibleRequested());
         assertTrue(token.isVisible());
-        mWm.mAtmService.getTransitionController().abort(transit);
+        transit.abort();
 
         // In a transition, setting invisible should ONLY set requestedVisible false; otherwise
         // wallpaper should remain "visible" until transition is over.
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index f85cdf0..07244a4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -66,6 +66,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -81,6 +82,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.SparseArray;
 import android.view.Display;
@@ -109,6 +111,7 @@
 
 import com.android.internal.policy.AttributeCache;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
 
 import org.junit.After;
@@ -146,6 +149,7 @@
     WindowManagerService mWm;
     private final IWindow mIWindow = new TestIWindow();
     private Session mMockSession;
+    private boolean mUseFakeSettingsProvider;
 
     DisplayInfo mDisplayInfo = new DisplayInfo();
     DisplayContent mDefaultDisplay;
@@ -272,16 +276,9 @@
 
     @After
     public void tearDown() throws Exception {
-        // Revert back to device overrides.
-        mAtm.mWindowManager.mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
-        mAtm.mWindowManager.mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
-        mAtm.mWindowManager.mLetterboxConfiguration.resetLetterboxVerticalPositionMultiplier();
-        mAtm.mWindowManager.mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled();
-        mAtm.mWindowManager.mLetterboxConfiguration.resetIsVerticalReachabilityEnabled();
-        mAtm.mWindowManager.mLetterboxConfiguration
-                .resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
-        mAtm.mWindowManager.mLetterboxConfiguration
-                .resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
+        if (mUseFakeSettingsProvider) {
+            FakeSettingsProvider.clearSettingsProvider();
+        }
     }
 
     /**
@@ -428,6 +425,17 @@
         // Called before display is created.
     }
 
+    /** Avoid writing values to real Settings. */
+    ContentResolver useFakeSettingsProvider() {
+        mUseFakeSettingsProvider = true;
+        FakeSettingsProvider.clearSettingsProvider();
+        final FakeSettingsProvider provider = new FakeSettingsProvider();
+        // SystemServicesTestRule#setUpSystemCore has called spyOn for the ContentResolver.
+        final ContentResolver resolver = mContext.getContentResolver();
+        doReturn(provider.getIContentProvider()).when(resolver).acquireProvider(Settings.AUTHORITY);
+        return resolver;
+    }
+
     private WindowState createCommonWindow(WindowState parent, int type, String name) {
         final WindowState win = createWindow(parent, type, name);
         // Prevent common windows from been IME targets.
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerEvent.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerEvent.java
new file mode 100644
index 0000000..2a55496
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerEvent.java
@@ -0,0 +1,135 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger;
+
+import android.util.Slog;
+
+import com.android.server.utils.EventLogger.Event;
+
+import java.util.UUID;
+
+public abstract class SoundTriggerEvent extends Event {
+
+    @Override
+    public Event printLog(int type, String tag) {
+        switch (type) {
+            case ALOGI:
+                Slog.i(tag, eventToString());
+                break;
+            case ALOGE:
+                Slog.e(tag, eventToString());
+                break;
+            case ALOGW:
+                Slog.w(tag, eventToString());
+                break;
+            case ALOGV:
+            default:
+                Slog.v(tag, eventToString());
+        }
+        return this;
+    }
+
+    public static class ServiceEvent extends SoundTriggerEvent {
+        public enum Type {
+            ATTACH,
+            LIST_MODULE,
+            DETACH,
+        }
+
+        private final Type mType;
+        private final String mPackageName;
+        private final String mErrorString;
+
+        public ServiceEvent(Type type) {
+            this(type, null, null);
+        }
+
+        public ServiceEvent(Type type, String packageName) {
+            this(type, packageName, null);
+        }
+
+        public ServiceEvent(Type type, String packageName, String errorString) {
+            mType = type;
+            mPackageName = packageName;
+            mErrorString = errorString;
+        }
+
+        @Override
+        public String eventToString() {
+            var res = new StringBuilder(String.format("%-12s", mType.name()));
+            if (mErrorString != null) {
+                res.append(" ERROR: ").append(mErrorString);
+            }
+            if (mPackageName != null) {
+                res.append(" for: ").append(mPackageName);
+            }
+            return res.toString();
+        }
+    }
+
+    public static class SessionEvent extends SoundTriggerEvent {
+        public enum Type {
+            // Downward calls
+            START_RECOGNITION,
+            STOP_RECOGNITION,
+            LOAD_MODEL,
+            UNLOAD_MODEL,
+            UPDATE_MODEL,
+            DELETE_MODEL,
+            START_RECOGNITION_SERVICE,
+            STOP_RECOGNITION_SERVICE,
+            GET_MODEL_STATE,
+            SET_PARAMETER,
+            GET_MODULE_PROPERTIES,
+            DETACH,
+            // Callback events
+            RECOGNITION,
+            RESUME,
+            RESUME_FAILED,
+            PAUSE,
+            PAUSE_FAILED,
+            RESOURCES_AVAILABLE,
+            MODULE_DIED
+        }
+
+        private final UUID mModelUuid;
+        private final Type mType;
+        private final String mErrorString;
+
+        public SessionEvent(Type type, UUID modelUuid, String errorString) {
+            mType = type;
+            mModelUuid = modelUuid;
+            mErrorString = errorString;
+        }
+
+        public SessionEvent(Type type, UUID modelUuid) {
+            this(type, modelUuid, null);
+        }
+
+        @Override
+        public String eventToString() {
+            var res = new StringBuilder(String.format("%-25s", mType.name()));
+            if (mErrorString != null) {
+                res.append(" ERROR: ").append(mErrorString);
+            }
+            if (mModelUuid != null) {
+                res.append(" for: ").append(mModelUuid);
+            }
+            return res.toString();
+        }
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 07dc1c6..bee75df 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -16,6 +16,9 @@
 
 package com.android.server.soundtrigger;
 
+import static com.android.server.soundtrigger.SoundTriggerEvent.SessionEvent.Type;
+import static com.android.server.utils.EventLogger.Event.ALOGW;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -49,7 +52,11 @@
 import android.telephony.TelephonyManager;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
+import com.android.server.soundtrigger.SoundTriggerEvent.SessionEvent;
+import com.android.server.utils.EventLogger.Event;
+import com.android.server.utils.EventLogger;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -75,7 +82,6 @@
  */
 public class SoundTriggerHelper implements SoundTrigger.StatusListener {
     static final String TAG = "SoundTriggerHelper";
-    static final boolean DBG = false;
 
     // Module ID if there is no available module to connect to.
     public static final int INVALID_MODULE_ID = -1;
@@ -128,8 +134,12 @@
     private final int mModuleId;
     private final Function<SoundTrigger.StatusListener, SoundTriggerModule> mModuleProvider;
     private final Supplier<List<ModuleProperties>> mModulePropertiesProvider;
+    private final EventLogger mEventLogger;
 
-    SoundTriggerHelper(Context context,
+    @GuardedBy("mLock")
+    private boolean mIsDetached = false;
+
+    SoundTriggerHelper(Context context, EventLogger eventLogger,
             @NonNull Function<SoundTrigger.StatusListener, SoundTriggerModule> moduleProvider,
             int moduleId,
             @NonNull Supplier<List<ModuleProperties>> modulePropertiesProvider) {
@@ -140,6 +150,7 @@
         mModelDataMap = new HashMap<UUID, ModelData>();
         mKeyphraseUuidMap = new HashMap<Integer, UUID>();
         mModuleProvider = moduleProvider;
+        mEventLogger = eventLogger;
         mModulePropertiesProvider = modulePropertiesProvider;
         if (moduleId == INVALID_MODULE_ID) {
             mModule = null;
@@ -184,7 +195,7 @@
      * recognition.
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    int startGenericRecognition(UUID modelId, GenericSoundModel soundModel,
+    public int startGenericRecognition(UUID modelId, GenericSoundModel soundModel,
             IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
             boolean runInBatterySaverMode) {
         MetricsLogger.count(mContext, "sth_start_recognition", 1);
@@ -195,6 +206,9 @@
         }
 
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             ModelData modelData = getOrCreateGenericModelDataLocked(modelId);
             if (modelData == null) {
                 Slog.w(TAG, "Irrecoverable error occurred, check UUID / sound model data.");
@@ -214,7 +228,7 @@
      * @param callback The callback for the recognition events related to the given keyphrase.
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    int startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
+    public int startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
             IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
             boolean runInBatterySaverMode) {
         synchronized (mLock) {
@@ -223,12 +237,8 @@
                 return STATUS_ERROR;
             }
 
-            if (DBG) {
-                Slog.d(TAG, "startKeyphraseRecognition for keyphraseId=" + keyphraseId
-                        + " soundModel=" + soundModel + ", callback=" + callback.asBinder()
-                        + ", recognitionConfig=" + recognitionConfig
-                        + ", runInBatterySaverMode=" + runInBatterySaverMode);
-                dumpModelStateLocked();
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
             }
 
             ModelData model = getKeyphraseModelDataLocked(keyphraseId);
@@ -290,9 +300,6 @@
             }
             modelData.setHandle(handle[0]);
             modelData.setLoaded();
-            if (DBG) {
-                Slog.d(TAG, "prepareForRecognition: Sound model loaded with handle:" + handle[0]);
-            }
         }
         return STATUS_OK;
     }
@@ -311,7 +318,7 @@
      * for the recognition.
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    int startRecognition(SoundModel soundModel, ModelData modelData,
+    private int startRecognition(SoundModel soundModel, ModelData modelData,
             IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
             int keyphraseId, boolean runInBatterySaverMode) {
         synchronized (mLock) {
@@ -385,7 +392,7 @@
      *
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback) {
+    public int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback) {
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_stop_recognition", 1);
             if (callback == null || modelId == null) {
@@ -393,7 +400,9 @@
                         modelId);
                 return STATUS_ERROR;
             }
-
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             ModelData modelData = mModelDataMap.get(modelId);
             if (modelData == null || !modelData.isGenericModel()) {
                 Slog.w(TAG, "Attempting stopRecognition on invalid model with id:" + modelId);
@@ -418,7 +427,7 @@
      *
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback) {
+    public int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback) {
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_stop_recognition", 1);
             if (callback == null) {
@@ -426,20 +435,15 @@
                         keyphraseId);
                 return STATUS_ERROR;
             }
-
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
             if (modelData == null || !modelData.isKeyphraseModel()) {
                 Slog.w(TAG, "No model exists for given keyphrase Id " + keyphraseId);
                 return STATUS_ERROR;
             }
 
-            if (DBG) {
-                Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId + ", callback =" +
-                        callback.asBinder());
-                Slog.d(TAG, "current callback="
-                        + ((modelData == null || modelData.getCallback() == null) ? "null" :
-                            modelData.getCallback().asBinder()));
-            }
             int status = stopRecognition(modelData, callback);
             if (status != SoundTrigger.STATUS_OK) {
                 return status;
@@ -538,6 +542,11 @@
     }
 
     public ModuleProperties getModuleProperties() {
+        synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
+        }
         for (ModuleProperties moduleProperties : mModulePropertiesProvider.get()) {
             if (moduleProperties.getId() == mModuleId) {
                 return moduleProperties;
@@ -547,7 +556,7 @@
         return null;
     }
 
-    int unloadKeyphraseSoundModel(int keyphraseId) {
+    public int unloadKeyphraseSoundModel(int keyphraseId) {
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_unload_keyphrase_sound_model", 1);
             ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
@@ -555,7 +564,9 @@
                     || !modelData.isKeyphraseModel()) {
                 return STATUS_ERROR;
             }
-
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             // Stop recognition if it's the current one.
             modelData.setRequested(false);
             int status = updateRecognitionLocked(modelData, false);
@@ -574,12 +585,15 @@
         }
     }
 
-    int unloadGenericSoundModel(UUID modelId) {
+    public int unloadGenericSoundModel(UUID modelId) {
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_unload_generic_sound_model", 1);
             if (modelId == null || mModule == null) {
                 return STATUS_ERROR;
             }
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             ModelData modelData = mModelDataMap.get(modelId);
             if (modelData == null || !modelData.isGenericModel()) {
                 Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" +
@@ -610,24 +624,29 @@
 
             // Remove it from existence.
             mModelDataMap.remove(modelId);
-            if (DBG) dumpModelStateLocked();
             return status;
         }
     }
 
-    boolean isRecognitionRequested(UUID modelId) {
+    public boolean isRecognitionRequested(UUID modelId) {
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             ModelData modelData = mModelDataMap.get(modelId);
             return modelData != null && modelData.isRequested();
         }
     }
 
-    int getGenericModelState(UUID modelId) {
+    public int getGenericModelState(UUID modelId) {
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_get_generic_model_state", 1);
             if (modelId == null || mModule == null) {
                 return STATUS_ERROR;
             }
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             ModelData modelData = mModelDataMap.get(modelId);
             if (modelData == null || !modelData.isGenericModel()) {
                 Slog.w(TAG, "GetGenericModelState error: Invalid generic model id:" +
@@ -647,19 +666,20 @@
         }
     }
 
-    int getKeyphraseModelState(UUID modelId) {
-        Slog.w(TAG, "GetKeyphraseModelState error: Not implemented");
-        return STATUS_ERROR;
-    }
-
-    int setParameter(UUID modelId, @ModelParams int modelParam, int value) {
+    public int setParameter(UUID modelId, @ModelParams int modelParam, int value) {
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             return setParameterLocked(mModelDataMap.get(modelId), modelParam, value);
         }
     }
 
-    int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) {
+    public int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) {
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             return setParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam, value);
         }
     }
@@ -678,14 +698,20 @@
         return mModule.setParameter(modelData.getHandle(), modelParam, value);
     }
 
-    int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
+    public int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             return getParameterLocked(mModelDataMap.get(modelId), modelParam);
         }
     }
 
-    int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+    public int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             return getParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
         }
     }
@@ -707,15 +733,21 @@
     }
 
     @Nullable
-    ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
+    public ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             return queryParameterLocked(mModelDataMap.get(modelId), modelParam);
         }
     }
 
     @Nullable
-    ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+    public ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
         synchronized (mLock) {
+            if (mIsDetached) {
+                throw new IllegalStateException("SoundTriggerHelper has been detached");
+            }
             return queryParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
         }
     }
@@ -753,7 +785,6 @@
             return;
         }
 
-        if (DBG) Slog.d(TAG, "onRecognition: " + event);
         synchronized (mLock) {
             switch (event.status) {
                 case SoundTrigger.RECOGNITION_STATUS_ABORT:
@@ -801,12 +832,14 @@
         }
 
         try {
+            mEventLogger.enqueue(new SessionEvent(Type.RECOGNITION, model.getModelId()));
             callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
-        } catch (DeadObjectException e) {
+        } catch (RemoteException e) {
+            mEventLogger.enqueue(new SessionEvent(
+                        Type.RECOGNITION, model.getModelId(), "RemoteException")
+                    .printLog(ALOGW, TAG));
             forceStopAndUnloadModelLocked(model, e);
             return;
-        } catch (RemoteException e) {
-            Slog.w(TAG, "RemoteException in onGenericSoundTriggerDetected", e);
         }
 
         RecognitionConfig config = model.getRecognitionConfig();
@@ -825,7 +858,6 @@
 
     @Override
     public void onModelUnloaded(int modelHandle) {
-        if (DBG) Slog.d(TAG, "onModelUnloaded: " + modelHandle);
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_sound_model_updated", 1);
             onModelUnloadedLocked(modelHandle);
@@ -834,7 +866,6 @@
 
     @Override
     public void onResourcesAvailable() {
-        if (DBG) Slog.d(TAG, "onResourcesAvailable");
         synchronized (mLock) {
             onResourcesAvailableLocked();
         }
@@ -876,6 +907,7 @@
     }
 
     private void onResourcesAvailableLocked() {
+        mEventLogger.enqueue(new SessionEvent(Type.RESOURCES_AVAILABLE, null));
         updateAllRecognitionsLocked();
     }
 
@@ -888,12 +920,14 @@
             try {
                 IRecognitionStatusCallback callback = modelData.getCallback();
                 if (callback != null) {
+                    mEventLogger.enqueue(new SessionEvent(Type.PAUSE, modelData.getModelId()));
                     callback.onRecognitionPaused();
                 }
-            } catch (DeadObjectException e) {
-                forceStopAndUnloadModelLocked(modelData, e);
             } catch (RemoteException e) {
-                Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
+                mEventLogger.enqueue(new SessionEvent(
+                            Type.PAUSE, modelData.getModelId(), "RemoteException")
+                        .printLog(ALOGW, TAG));
+                forceStopAndUnloadModelLocked(modelData, e);
             }
             updateRecognitionLocked(modelData, true);
         }
@@ -935,12 +969,14 @@
         }
 
         try {
+            mEventLogger.enqueue(new SessionEvent(Type.RECOGNITION, modelData.getModelId()));
             modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
-        } catch (DeadObjectException e) {
+        } catch (RemoteException e) {
+            mEventLogger.enqueue(new SessionEvent(
+                        Type.RECOGNITION, modelData.getModelId(), "RemoteException")
+                    .printLog(ALOGW, TAG));
             forceStopAndUnloadModelLocked(modelData, e);
             return;
-        } catch (RemoteException e) {
-            Slog.w(TAG, "RemoteException in onKeyphraseDetected", e);
         }
 
         RecognitionConfig config = modelData.getRecognitionConfig();
@@ -992,10 +1028,13 @@
                 IRecognitionStatusCallback callback = modelData.getCallback();
                 if (callback != null) {
                     try {
+                        mEventLogger.enqueue(new SessionEvent(Type.MODULE_DIED,
+                                    modelData.getModelId()).printLog(ALOGW, TAG));
                         callback.onModuleDied();
                     } catch (RemoteException e) {
-                        Slog.w(TAG, "RemoteException send moduleDied for model handle " +
-                                modelData.getHandle(), e);
+                        mEventLogger.enqueue(new SessionEvent(Type.MODULE_DIED,
+                                    modelData.getModelId(), "RemoteException")
+                                .printLog(ALOGW, TAG));
                     }
                 }
             }
@@ -1041,7 +1080,6 @@
 
         @Override
         public void onCallStateChanged(int state, String arg1) {
-            if (DBG) Slog.d(TAG, "onCallStateChanged: " + state);
 
             if (mHandler != null) {
                 synchronized (mLock) {
@@ -1063,24 +1101,12 @@
             }
             @SoundTriggerPowerSaveMode int soundTriggerPowerSaveMode =
                     mPowerManager.getSoundTriggerPowerSaveMode();
-            if (DBG) {
-                Slog.d(TAG, "onPowerSaveModeChanged: " + soundTriggerPowerSaveMode);
-            }
             synchronized (mLock) {
                 onPowerSaveModeChangedLocked(soundTriggerPowerSaveMode);
             }
         }
     }
 
-    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        synchronized (mLock) {
-            pw.print("  module properties=");
-            pw.print("  call active=");
-            pw.println(mCallActive);
-            pw.println("  SoundTrigger Power State=" + mSoundTriggerPowerSaveMode);
-        }
-    }
-
     private void initializeDeviceStateListeners() {
         if (mRecognitionRequested) {
             return;
@@ -1115,6 +1141,8 @@
      */
     public void detach() {
         synchronized (mLock) {
+            if (mIsDetached) return;
+            mIsDetached = true;
             for (ModelData model : mModelDataMap.values()) {
                 forceStopAndUnloadModelLocked(model, null);
             }
@@ -1289,7 +1317,7 @@
      * @param modelData Model data to be used for recognition
      * @return True if device state allows recognition to run, false if not.
      */
-    boolean isRecognitionAllowedByPowerState(ModelData modelData) {
+    private boolean isRecognitionAllowedByPowerState(ModelData modelData) {
         return mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED
                 || (mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY
                 && modelData.shouldRunInBatterySaverMode());
@@ -1324,11 +1352,16 @@
             // Notify of error if needed.
             if (notifyClientOnError) {
                 try {
+                    mEventLogger.enqueue(new SessionEvent(Type.RESUME_FAILED,
+                                modelData.getModelId(), String.valueOf(status))
+                            .printLog(ALOGW, TAG));
                     callback.onResumeFailed(status);
-                } catch (DeadObjectException e) {
-                    forceStopAndUnloadModelLocked(modelData, e);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onResumeFailed", e);
+                    mEventLogger.enqueue(new SessionEvent(Type.RESUME_FAILED,
+                                modelData.getModelId(),
+                                String.valueOf(status) + " - RemoteException")
+                            .printLog(ALOGW, TAG));
+                    forceStopAndUnloadModelLocked(modelData, e);
                 }
             }
         } else {
@@ -1338,17 +1371,16 @@
             // Notify of resume if needed.
             if (notifyClientOnError) {
                 try {
+                    mEventLogger.enqueue(new SessionEvent(Type.RESUME,
+                                modelData.getModelId()));
                     callback.onRecognitionResumed();
-                } catch (DeadObjectException e) {
-                    forceStopAndUnloadModelLocked(modelData, e);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
+                    mEventLogger.enqueue(new SessionEvent(Type.RESUME,
+                                modelData.getModelId(), "RemoteException").printLog(ALOGW, TAG));
+                    forceStopAndUnloadModelLocked(modelData, e);
                 }
             }
         }
-        if (DBG) {
-            Slog.d(TAG, "Model being started :" + modelData.toString());
-        }
         return status;
     }
 
@@ -1368,11 +1400,16 @@
             MetricsLogger.count(mContext, "sth_stop_recognition_error", 1);
             if (notify) {
                 try {
+                    mEventLogger.enqueue(new SessionEvent(Type.PAUSE_FAILED,
+                                modelData.getModelId(), String.valueOf(status))
+                            .printLog(ALOGW, TAG));
                     callback.onPauseFailed(status);
-                } catch (DeadObjectException e) {
-                    forceStopAndUnloadModelLocked(modelData, e);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onPauseFailed", e);
+                    mEventLogger.enqueue(new SessionEvent(Type.PAUSE_FAILED,
+                                modelData.getModelId(),
+                                String.valueOf(status) + " - RemoteException")
+                            .printLog(ALOGW, TAG));
+                    forceStopAndUnloadModelLocked(modelData, e);
                 }
             }
         } else {
@@ -1381,27 +1418,19 @@
             // Notify of pause if needed.
             if (notify) {
                 try {
+                    mEventLogger.enqueue(new SessionEvent(Type.PAUSE,
+                                modelData.getModelId()));
                     callback.onRecognitionPaused();
-                } catch (DeadObjectException e) {
-                    forceStopAndUnloadModelLocked(modelData, e);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
+                    mEventLogger.enqueue(new SessionEvent(Type.PAUSE,
+                                modelData.getModelId(), "RemoteException").printLog(ALOGW, TAG));
+                    forceStopAndUnloadModelLocked(modelData, e);
                 }
             }
         }
-        if (DBG) {
-            Slog.d(TAG, "Model being stopped :" + modelData.toString());
-        }
         return status;
     }
 
-    private void dumpModelStateLocked() {
-        for (UUID modelId : mModelDataMap.keySet()) {
-            ModelData modelData = mModelDataMap.get(modelId);
-            Slog.i(TAG, "Model :" + modelData.toString());
-        }
-    }
-
     // Computes whether we have any recognition running at all (voice or generic). Sets
     // the mRecognitionRequested variable with the result.
     private boolean computeRecognitionRequestedLocked() {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 790be8d..77e5317 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -31,6 +31,9 @@
 import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY;
 import static android.provider.Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT;
 
+import static com.android.server.soundtrigger.SoundTriggerEvent.SessionEvent.Type;
+import static com.android.server.utils.EventLogger.Event.ALOGW;
+
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.Manifest;
@@ -83,6 +86,7 @@
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.SparseArray;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -90,6 +94,9 @@
 import com.android.internal.app.ISoundTriggerSession;
 import com.android.server.SoundTriggerInternal;
 import com.android.server.SystemService;
+import com.android.server.soundtrigger.SoundTriggerEvent.ServiceEvent;
+import com.android.server.soundtrigger.SoundTriggerEvent.SessionEvent;
+import com.android.server.utils.EventLogger.Event;
 import com.android.server.utils.EventLogger;
 
 import java.io.FileDescriptor;
@@ -97,11 +104,16 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
+import java.util.Deque;
 import java.util.Map;
 import java.util.Objects;
 import java.util.TreeMap;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.LinkedBlockingDeque;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
 /**
@@ -116,6 +128,7 @@
 public class SoundTriggerService extends SystemService {
     private static final String TAG = "SoundTriggerService";
     private static final boolean DEBUG = true;
+    private static final int SESSION_MAX_EVENT_SIZE = 128;
 
     final Context mContext;
     private Object mLock;
@@ -123,6 +136,12 @@
     private final LocalSoundTriggerService mLocalSoundTriggerService;
     private SoundTriggerDbHelper mDbHelper;
 
+    private final EventLogger mServiceEventLogger = new EventLogger(256, "Service");
+
+    private final Set<EventLogger> mSessionEventLoggers = ConcurrentHashMap.newKeySet(4);
+    private final Deque<EventLogger> mDetachedSessionEventLoggers = new LinkedBlockingDeque<>(4);
+    private AtomicInteger mSessionIdCounter = new AtomicInteger(0);
+
     class SoundModelStatTracker {
         private class SoundModelStat {
             SoundModelStat() {
@@ -164,7 +183,7 @@
         public synchronized void onStop(UUID id) {
             SoundModelStat stat = mModelStats.get(id);
             if (stat == null) {
-                Slog.w(TAG, "error onStop(): Model " + id + " has no stats available");
+                Slog.i(TAG, "error onStop(): Model " + id + " has no stats available");
                 return;
             }
 
@@ -241,7 +260,9 @@
         }
     }
 
-    private SoundTriggerHelper newSoundTriggerHelper(ModuleProperties moduleProperties) {
+    private SoundTriggerHelper newSoundTriggerHelper(
+            ModuleProperties moduleProperties, EventLogger eventLogger) {
+
         Identity middlemanIdentity = new Identity();
         middlemanIdentity.packageName = ActivityThread.currentOpPackageName();
         Identity originatorIdentity = IdentityContext.getNonNull();
@@ -260,6 +281,7 @@
 
         return new SoundTriggerHelper(
                 mContext,
+                eventLogger,
                 (SoundTrigger.StatusListener statusListener) ->
                                         SoundTrigger.attachModuleAsMiddleman(
                                         moduleId, statusListener, null /* handler */,
@@ -269,14 +291,33 @@
                 );
     }
 
+    // Helper to add session logger to the capacity limited detached list.
+    // If we are at capacity, remove the oldest, and retry
+    private void addDetachedSessionLogger(EventLogger logger) {
+        // Attempt to push to the top of the queue
+        while (!mDetachedSessionEventLoggers.offerFirst(logger)) {
+            // Remove the oldest element, if one still exists
+            mDetachedSessionEventLoggers.pollLast();
+        }
+    }
+
     class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
         @Override
         public ISoundTriggerSession attachAsOriginator(@NonNull Identity originatorIdentity,
                 @NonNull ModuleProperties moduleProperties,
                 @NonNull IBinder client) {
+
+            int sessionId = mSessionIdCounter.getAndIncrement();
+            mServiceEventLogger.enqueue(new ServiceEvent(
+                    ServiceEvent.Type.ATTACH, originatorIdentity.packageName + "#" + sessionId));
             try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
                     originatorIdentity)) {
-                return new SoundTriggerSessionStub(client, newSoundTriggerHelper(moduleProperties));
+                var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE,
+                        "SoundTriggerSessionLogs for package: "
+                        + Objects.requireNonNull(originatorIdentity.packageName)
+                        + "#" + sessionId);
+                return new SoundTriggerSessionStub(client,
+                        newSoundTriggerHelper(moduleProperties, eventLogger), eventLogger);
             }
         }
 
@@ -285,15 +326,26 @@
                 @NonNull Identity middlemanIdentity,
                 @NonNull ModuleProperties moduleProperties,
                 @NonNull IBinder client) {
+
+            int sessionId = mSessionIdCounter.getAndIncrement();
+            mServiceEventLogger.enqueue(new ServiceEvent(
+                    ServiceEvent.Type.ATTACH, originatorIdentity.packageName + "#" + sessionId));
             try (SafeCloseable ignored = PermissionUtil.establishIdentityIndirect(mContext,
                     SOUNDTRIGGER_DELEGATE_IDENTITY, middlemanIdentity,
                     originatorIdentity)) {
-                return new SoundTriggerSessionStub(client, newSoundTriggerHelper(moduleProperties));
+                var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE,
+                        "SoundTriggerSessionLogs for package: "
+                        + Objects.requireNonNull(originatorIdentity.packageName) + "#"
+                        + sessionId);
+                return new SoundTriggerSessionStub(client,
+                        newSoundTriggerHelper(moduleProperties, eventLogger), eventLogger);
             }
         }
 
         @Override
         public List<ModuleProperties> listModuleProperties(@NonNull Identity originatorIdentity) {
+            mServiceEventLogger.enqueue(new ServiceEvent(
+                    ServiceEvent.Type.LIST_MODULE, originatorIdentity.packageName));
             try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
                     originatorIdentity)) {
                 return listUnderlyingModuleProperties(originatorIdentity);
@@ -316,6 +368,31 @@
                 throw e.rethrowFromSystemServer();
             }
         }
+
+        @Override
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            // Event loggers
+            pw.println("##Service-Wide logs:");
+            mServiceEventLogger.dump(pw, /* indent = */ "  ");
+
+            pw.println("\n##Active Session dumps:\n");
+            for (var sessionLogger : mSessionEventLoggers) {
+                sessionLogger.dump(pw, /* indent= */ "  ");
+                pw.println("");
+            }
+            pw.println("##Detached Session dumps:\n");
+            for (var sessionLogger : mDetachedSessionEventLoggers) {
+                sessionLogger.dump(pw, /* indent= */ "  ");
+                pw.println("");
+            }
+            // enrolled models
+            pw.println("##Enrolled db dump:\n");
+            mDbHelper.dump(pw);
+
+            // stats
+            pw.println("\n##Sound Model Stats dump:\n");
+            mSoundModelStatTracker.dump(pw);
+        }
     }
 
     class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
@@ -326,17 +403,20 @@
         private final TreeMap<UUID, SoundModel> mLoadedModels = new TreeMap<>();
         private final Object mCallbacksLock = new Object();
         private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks = new TreeMap<>();
+        private final EventLogger mEventLogger;
 
-        SoundTriggerSessionStub(@NonNull IBinder client, SoundTriggerHelper soundTriggerHelper) {
+        SoundTriggerSessionStub(@NonNull IBinder client,
+                SoundTriggerHelper soundTriggerHelper, EventLogger eventLogger) {
             mSoundTriggerHelper = soundTriggerHelper;
             mClient = client;
             mOriginatorIdentity = IdentityContext.getNonNull();
+            mEventLogger = eventLogger;
+            mSessionEventLoggers.add(mEventLogger);
+
             try {
-                mClient.linkToDeath(() -> {
-                    clientDied();
-                }, 0);
+                mClient.linkToDeath(() -> clientDied(), 0);
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to register death listener.", e);
+                clientDied();
             }
         }
 
@@ -344,11 +424,14 @@
         public int startRecognition(GenericSoundModel soundModel,
                 IRecognitionStatusCallback callback,
                 RecognitionConfig config, boolean runInBatterySaverMode) {
+            mEventLogger.enqueue(new SessionEvent(Type.START_RECOGNITION, getUuid(soundModel)));
+
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
 
                 if (soundModel == null) {
-                    Slog.e(TAG, "Null model passed to startRecognition");
+                    mEventLogger.enqueue(new SessionEvent(Type.START_RECOGNITION,
+                                getUuid(soundModel), "Invalid sound model").printLog(ALOGW, TAG));
                     return STATUS_ERROR;
                 }
 
@@ -356,13 +439,6 @@
                     enforceCallingPermission(Manifest.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER);
                 }
 
-                if (DEBUG) {
-                    Slog.i(TAG, "startRecognition(): Uuid : " + soundModel.toString());
-                }
-
-                sEventLogger.enqueue(new EventLogger.StringEvent(
-                        "startRecognition(): Uuid : " + soundModel.getUuid().toString()));
-
                 int ret = mSoundTriggerHelper.startGenericRecognition(soundModel.getUuid(),
                         soundModel,
                         callback, config, runInBatterySaverMode);
@@ -375,15 +451,9 @@
 
         @Override
         public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
+            mEventLogger.enqueue(new SessionEvent(Type.STOP_RECOGNITION, getUuid(parcelUuid)));
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
-                if (DEBUG) {
-                    Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
-                }
-
-                sEventLogger.enqueue(new EventLogger.StringEvent("stopRecognition(): Uuid : "
-                        + parcelUuid));
-
                 int ret = mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(),
                         callback);
                 if (ret == STATUS_OK) {
@@ -397,13 +467,6 @@
         public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) {
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
-                if (DEBUG) {
-                    Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
-                }
-
-                sEventLogger.enqueue(new EventLogger.StringEvent("getSoundModel(): id = "
-                        + soundModelId));
-
                 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
                         soundModelId.getUuid());
                 return model;
@@ -412,29 +475,18 @@
 
         @Override
         public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) {
+            mEventLogger.enqueue(new SessionEvent(Type.UPDATE_MODEL, getUuid(soundModel)));
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
-                if (DEBUG) {
-                    Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
-                }
-
-                sEventLogger.enqueue(new EventLogger.StringEvent("updateSoundModel(): model = "
-                        + soundModel));
-               mDbHelper.updateGenericSoundModel(soundModel);
+                mDbHelper.updateGenericSoundModel(soundModel);
             }
         }
 
         @Override
         public void deleteSoundModel(ParcelUuid soundModelId) {
+            mEventLogger.enqueue(new SessionEvent(Type.DELETE_MODEL, getUuid(soundModelId)));
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
-                if (DEBUG) {
-                    Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
-                }
-
-                sEventLogger.enqueue(new EventLogger.StringEvent("deleteSoundModel(): id = "
-                        + soundModelId));
-
                 // Unload the model if it is loaded.
                 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
 
@@ -447,22 +499,14 @@
 
         @Override
         public int loadGenericSoundModel(GenericSoundModel soundModel) {
+            mEventLogger.enqueue(new SessionEvent(Type.LOAD_MODEL, getUuid(soundModel)));
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
                 if (soundModel == null || soundModel.getUuid() == null) {
-                    Slog.w(TAG, "Invalid sound model");
-
-                    sEventLogger.enqueue(new EventLogger.StringEvent(
-                            "loadGenericSoundModel(): Invalid sound model"));
-
+                    mEventLogger.enqueue(new SessionEvent(Type.LOAD_MODEL,
+                                getUuid(soundModel), "Invalid sound model").printLog(ALOGW, TAG));
                     return STATUS_ERROR;
                 }
-                if (DEBUG) {
-                    Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.getUuid());
-                }
-
-                sEventLogger.enqueue(new EventLogger.StringEvent("loadGenericSoundModel(): id = "
-                        + soundModel.getUuid()));
 
                 synchronized (mLock) {
                     SoundModel oldModel = mLoadedModels.get(soundModel.getUuid());
@@ -483,32 +527,22 @@
 
         @Override
         public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
+            mEventLogger.enqueue(new SessionEvent(Type.LOAD_MODEL, getUuid(soundModel)));
+
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
                 if (soundModel == null || soundModel.getUuid() == null) {
-                    Slog.w(TAG, "Invalid sound model");
-
-                    sEventLogger.enqueue(new EventLogger.StringEvent(
-                            "loadKeyphraseSoundModel(): Invalid sound model"));
+                    mEventLogger.enqueue(new SessionEvent(Type.LOAD_MODEL, getUuid(soundModel),
+                                "Invalid sound model").printLog(ALOGW, TAG));
 
                     return STATUS_ERROR;
                 }
                 if (soundModel.getKeyphrases() == null || soundModel.getKeyphrases().length != 1) {
-                    Slog.w(TAG, "Only one keyphrase per model is currently supported.");
-
-                    sEventLogger.enqueue(new EventLogger.StringEvent(
-                            "loadKeyphraseSoundModel(): Only one keyphrase per model"
-                                    + " is currently supported."));
-
+                    mEventLogger.enqueue(new SessionEvent(Type.LOAD_MODEL, getUuid(soundModel),
+                                "Only one keyphrase supported").printLog(ALOGW, TAG));
                     return STATUS_ERROR;
                 }
-                if (DEBUG) {
-                    Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.getUuid());
-                }
 
-                sEventLogger.enqueue(
-                        new EventLogger.StringEvent("loadKeyphraseSoundModel(): id = "
-                                + soundModel.getUuid()));
 
                 synchronized (mLock) {
                     SoundModel oldModel = mLoadedModels.get(soundModel.getUuid());
@@ -530,23 +564,17 @@
 
         @Override
         public int startRecognitionForService(ParcelUuid soundModelId, Bundle params,
-            ComponentName detectionService, SoundTrigger.RecognitionConfig config) {
+                ComponentName detectionService, SoundTrigger.RecognitionConfig config) {
+            mEventLogger.enqueue(new SessionEvent(Type.START_RECOGNITION_SERVICE,
+                        getUuid(soundModelId)));
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 Objects.requireNonNull(soundModelId);
                 Objects.requireNonNull(detectionService);
                 Objects.requireNonNull(config);
 
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
-
                 enforceDetectionPermissions(detectionService);
 
-                if (DEBUG) {
-                    Slog.i(TAG, "startRecognition(): id = " + soundModelId);
-                }
-
-                sEventLogger.enqueue(new EventLogger.StringEvent(
-                        "startRecognitionForService(): id = " + soundModelId));
-
                 IRecognitionStatusCallback callback =
                         new RemoteSoundTriggerDetectionService(soundModelId.getUuid(), params,
                                 detectionService, Binder.getCallingUserHandle(), config);
@@ -554,10 +582,10 @@
                 synchronized (mLock) {
                     SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
                     if (soundModel == null) {
-                        Slog.w(TAG, soundModelId + " is not loaded");
-
-                        sEventLogger.enqueue(new EventLogger.StringEvent(
-                                "startRecognitionForService():" + soundModelId + " is not loaded"));
+                        mEventLogger.enqueue(new SessionEvent(
+                                    Type.START_RECOGNITION_SERVICE,
+                                    getUuid(soundModelId),
+                                    "Model not loaded").printLog(ALOGW, TAG));
 
                         return STATUS_ERROR;
                     }
@@ -566,12 +594,10 @@
                         existingCallback = mCallbacks.get(soundModelId.getUuid());
                     }
                     if (existingCallback != null) {
-                        Slog.w(TAG, soundModelId + " is already running");
-
-                        sEventLogger.enqueue(new EventLogger.StringEvent(
-                                "startRecognitionForService():"
-                                        + soundModelId + " is already running"));
-
+                        mEventLogger.enqueue(new SessionEvent(
+                                    Type.START_RECOGNITION_SERVICE,
+                                    getUuid(soundModelId),
+                                    "Model already running").printLog(ALOGW, TAG));
                         return STATUS_ERROR;
                     }
                     int ret;
@@ -581,20 +607,18 @@
                                     (GenericSoundModel) soundModel, callback, config, false);
                             break;
                         default:
-                            Slog.e(TAG, "Unknown model type");
-
-                            sEventLogger.enqueue(new EventLogger.StringEvent(
-                                    "startRecognitionForService(): Unknown model type"));
-
+                            mEventLogger.enqueue(new SessionEvent(
+                                        Type.START_RECOGNITION_SERVICE,
+                                        getUuid(soundModelId),
+                                        "Unsupported model type").printLog(ALOGW, TAG));
                             return STATUS_ERROR;
                     }
 
                     if (ret != STATUS_OK) {
-                        Slog.e(TAG, "Failed to start model: " + ret);
-
-                        sEventLogger.enqueue(new EventLogger.StringEvent(
-                                "startRecognitionForService(): Failed to start model:"));
-
+                        mEventLogger.enqueue(new SessionEvent(
+                                    Type.START_RECOGNITION_SERVICE,
+                                    getUuid(soundModelId),
+                                    "Model start fail").printLog(ALOGW, TAG));
                         return ret;
                     }
                     synchronized (mCallbacksLock) {
@@ -609,23 +633,20 @@
 
         @Override
         public int stopRecognitionForService(ParcelUuid soundModelId) {
+            mEventLogger.enqueue(new SessionEvent(Type.STOP_RECOGNITION_SERVICE,
+                        getUuid(soundModelId)));
+
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
-                if (DEBUG) {
-                    Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
-                }
-
-                sEventLogger.enqueue(new EventLogger.StringEvent(
-                        "stopRecognitionForService(): id = " + soundModelId));
 
                 synchronized (mLock) {
                     SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
                     if (soundModel == null) {
-                        Slog.w(TAG, soundModelId + " is not loaded");
-
-                        sEventLogger.enqueue(new EventLogger.StringEvent(
-                                "stopRecognitionForService(): " + soundModelId
-                                        + " is not loaded"));
+                        mEventLogger.enqueue(new SessionEvent(
+                                    Type.STOP_RECOGNITION_SERVICE,
+                                    getUuid(soundModelId),
+                                    "Model not loaded")
+                                .printLog(ALOGW, TAG));
 
                         return STATUS_ERROR;
                     }
@@ -634,12 +655,11 @@
                         callback = mCallbacks.get(soundModelId.getUuid());
                     }
                     if (callback == null) {
-                        Slog.w(TAG, soundModelId + " is not running");
-
-                        sEventLogger.enqueue(new EventLogger.StringEvent(
-                                "stopRecognitionForService(): " + soundModelId
-                                        + " is not running"));
-
+                        mEventLogger.enqueue(new SessionEvent(
+                                    Type.STOP_RECOGNITION_SERVICE,
+                                    getUuid(soundModelId),
+                                    "Model not running")
+                                .printLog(ALOGW, TAG));
                         return STATUS_ERROR;
                     }
                     int ret;
@@ -649,20 +669,21 @@
                                     soundModel.getUuid(), callback);
                             break;
                         default:
-                            Slog.e(TAG, "Unknown model type");
-
-                            sEventLogger.enqueue(new EventLogger.StringEvent(
-                                    "stopRecognitionForService(): Unknown model type"));
+                            mEventLogger.enqueue(new SessionEvent(
+                                        Type.STOP_RECOGNITION_SERVICE,
+                                        getUuid(soundModelId),
+                                        "Unknown model type")
+                                    .printLog(ALOGW, TAG));
 
                             return STATUS_ERROR;
                     }
 
                     if (ret != STATUS_OK) {
-                        Slog.e(TAG, "Failed to stop model: " + ret);
-
-                        sEventLogger.enqueue(new EventLogger.StringEvent(
-                                "stopRecognitionForService(): Failed to stop model: " + ret));
-
+                        mEventLogger.enqueue(new SessionEvent(
+                                    Type.STOP_RECOGNITION_SERVICE,
+                                    getUuid(soundModelId),
+                                    "Failed to stop model")
+                                .printLog(ALOGW, TAG));
                         return ret;
                     }
                     synchronized (mCallbacksLock) {
@@ -677,23 +698,18 @@
 
         @Override
         public int unloadSoundModel(ParcelUuid soundModelId) {
+            mEventLogger.enqueue(new SessionEvent(Type.UNLOAD_MODEL, getUuid(soundModelId)));
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
-                if (DEBUG) {
-                    Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
-                }
-
-                sEventLogger.enqueue(new EventLogger.StringEvent("unloadSoundModel(): id = "
-                        + soundModelId));
 
                 synchronized (mLock) {
                     SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
                     if (soundModel == null) {
-                        Slog.w(TAG, soundModelId + " is not loaded");
-
-                        sEventLogger.enqueue(new EventLogger.StringEvent(
-                                "unloadSoundModel(): " + soundModelId + " is not loaded"));
-
+                        mEventLogger.enqueue(new SessionEvent(
+                                    Type.UNLOAD_MODEL,
+                                    getUuid(soundModelId),
+                                    "Model not loaded")
+                                .printLog(ALOGW, TAG));
                         return STATUS_ERROR;
                     }
                     int ret;
@@ -706,19 +722,19 @@
                             ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.getUuid());
                             break;
                         default:
-                            Slog.e(TAG, "Unknown model type");
-
-                            sEventLogger.enqueue(new EventLogger.StringEvent(
-                                    "unloadSoundModel(): Unknown model type"));
-
+                            mEventLogger.enqueue(new SessionEvent(
+                                        Type.UNLOAD_MODEL,
+                                        getUuid(soundModelId),
+                                        "Unknown model type")
+                                    .printLog(ALOGW, TAG));
                             return STATUS_ERROR;
                     }
                     if (ret != STATUS_OK) {
-                        Slog.e(TAG, "Failed to unload model");
-
-                        sEventLogger.enqueue(new EventLogger.StringEvent(
-                                "unloadSoundModel(): Failed to unload model"));
-
+                        mEventLogger.enqueue(new SessionEvent(
+                                    Type.UNLOAD_MODEL,
+                                    getUuid(soundModelId),
+                                    "Failed to unload model")
+                                .printLog(ALOGW, TAG));
                         return ret;
                     }
                     mLoadedModels.remove(soundModelId.getUuid());
@@ -743,24 +759,19 @@
 
         @Override
         public int getModelState(ParcelUuid soundModelId) {
+            mEventLogger.enqueue(new SessionEvent(Type.GET_MODEL_STATE, getUuid(soundModelId)));
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
                 int ret = STATUS_ERROR;
-                if (DEBUG) {
-                    Slog.i(TAG, "getModelState(): id = " + soundModelId);
-                }
-
-                sEventLogger.enqueue(new EventLogger.StringEvent("getModelState(): id = "
-                        + soundModelId));
 
                 synchronized (mLock) {
                     SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
                     if (soundModel == null) {
-                        Slog.w(TAG, soundModelId + " is not loaded");
-
-                        sEventLogger.enqueue(new EventLogger.StringEvent("getModelState(): "
-                                + soundModelId + " is not loaded"));
-
+                        mEventLogger.enqueue(new SessionEvent(
+                                    Type.GET_MODEL_STATE,
+                                    getUuid(soundModelId),
+                                    "Model is not loaded")
+                                .printLog(ALOGW, TAG));
                         return ret;
                     }
                     switch (soundModel.getType()) {
@@ -769,13 +780,13 @@
                             break;
                         default:
                             // SoundModel.TYPE_KEYPHRASE is not supported to increase privacy.
-                            Slog.e(TAG, "Unsupported model type, " + soundModel.getType());
-                            sEventLogger.enqueue(new EventLogger.StringEvent(
-                                    "getModelState(): Unsupported model type, "
-                                            + soundModel.getType()));
+                            mEventLogger.enqueue(new SessionEvent(
+                                        Type.GET_MODEL_STATE,
+                                        getUuid(soundModelId),
+                                        "Unsupported model type")
+                                .printLog(ALOGW, TAG));
                             break;
                     }
-
                     return ret;
                 }
             }
@@ -784,16 +795,11 @@
         @Override
         @Nullable
         public ModuleProperties getModuleProperties() {
+            mEventLogger.enqueue(new SessionEvent(Type.GET_MODULE_PROPERTIES, null));
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
-                if (DEBUG) {
-                    Slog.i(TAG, "getModuleProperties()");
-                }
-
                 synchronized (mLock) {
                     ModuleProperties properties = mSoundTriggerHelper.getModuleProperties();
-                    sEventLogger.enqueue(new EventLogger.StringEvent(
-                            "getModuleProperties(): " + properties));
                     return properties;
                 }
             }
@@ -802,33 +808,21 @@
         @Override
         public int setParameter(ParcelUuid soundModelId,
                 @ModelParams int modelParam, int value) {
+            mEventLogger.enqueue(new SessionEvent(Type.SET_PARAMETER, getUuid(soundModelId)));
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
-                if (DEBUG) {
-                    Slog.d(TAG, "setParameter(): id=" + soundModelId
-                            + ", param=" + modelParam
-                            + ", value=" + value);
-                }
-
-                sEventLogger.enqueue(new EventLogger.StringEvent(
-                        "setParameter(): id=" + soundModelId
-                                + ", param=" + modelParam
-                                + ", value=" + value));
-
                 synchronized (mLock) {
                     SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
                     if (soundModel == null) {
-                        Slog.w(TAG, soundModelId + " is not loaded. Loaded models: "
-                                + mLoadedModels.toString());
-
-                        sEventLogger.enqueue(new EventLogger.StringEvent("setParameter(): "
-                                + soundModelId + " is not loaded"));
-
+                        mEventLogger.enqueue(new SessionEvent(
+                                    Type.SET_PARAMETER,
+                                    getUuid(soundModelId),
+                                    "Model not loaded")
+                                .printLog(ALOGW, TAG));
                         return STATUS_BAD_VALUE;
                     }
-
-                    return mSoundTriggerHelper.setParameter(soundModel.getUuid(), modelParam,
-                            value);
+                    return mSoundTriggerHelper.setParameter(
+                            soundModel.getUuid(), modelParam, value);
                 }
             }
         }
@@ -839,26 +833,11 @@
                 throws UnsupportedOperationException, IllegalArgumentException {
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
-                if (DEBUG) {
-                    Slog.d(TAG, "getParameter(): id=" + soundModelId
-                            + ", param=" + modelParam);
-                }
-
-                sEventLogger.enqueue(new EventLogger.StringEvent(
-                        "getParameter(): id=" + soundModelId
-                                + ", param=" + modelParam));
-
                 synchronized (mLock) {
                     SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
                     if (soundModel == null) {
-                        Slog.w(TAG, soundModelId + " is not loaded");
-
-                        sEventLogger.enqueue(new EventLogger.StringEvent("getParameter(): "
-                                + soundModelId + " is not loaded"));
-
                         throw new IllegalArgumentException("sound model is not loaded");
                     }
-
                     return mSoundTriggerHelper.getParameter(soundModel.getUuid(), modelParam);
                 }
             }
@@ -870,37 +849,28 @@
                 @ModelParams int modelParam) {
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
-                if (DEBUG) {
-                    Slog.d(TAG, "queryParameter(): id=" + soundModelId
-                            + ", param=" + modelParam);
-                }
-
-                sEventLogger.enqueue(new EventLogger.StringEvent(
-                        "queryParameter(): id=" + soundModelId
-                                + ", param=" + modelParam));
-
                 synchronized (mLock) {
                     SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
                     if (soundModel == null) {
-                        Slog.w(TAG, soundModelId + " is not loaded");
-
-                        sEventLogger.enqueue(new EventLogger.StringEvent(
-                                "queryParameter(): "
-                                        + soundModelId + " is not loaded"));
-
                         return null;
                     }
-
                     return mSoundTriggerHelper.queryParameter(soundModel.getUuid(), modelParam);
                 }
             }
         }
 
         private void clientDied() {
-            Slog.w(TAG, "Client died, cleaning up session.");
-            sEventLogger.enqueue(new EventLogger.StringEvent(
-                    "Client died, cleaning up session."));
+            mEventLogger.enqueue(new SessionEvent(Type.DETACH, null));
+            mServiceEventLogger.enqueue(new ServiceEvent(
+                        ServiceEvent.Type.DETACH, mOriginatorIdentity.packageName, "Client died")
+                    .printLog(ALOGW, TAG));
+            detach();
+        }
+
+        private void detach() {
             mSoundTriggerHelper.detach();
+            mSessionEventLoggers.remove(mEventLogger);
+            addDetachedSessionLogger(mEventLogger);
         }
 
         private void enforceCallingPermission(String permission) {
@@ -922,6 +892,14 @@
             }
         }
 
+        private UUID getUuid(ParcelUuid uuid) {
+            return (uuid != null) ? uuid.getUuid() : null;
+        }
+
+        private UUID getUuid(SoundModel model) {
+            return (model != null) ? model.getUuid() : null;
+        }
+
         /**
          * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and
          * executed when the service connects.
@@ -1068,7 +1046,7 @@
                     } catch (Exception e) {
                         Slog.e(TAG, mPuuid + ": Cannot remove client", e);
 
-                        sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
+                        mEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                 + ": Cannot remove client"));
 
                     }
@@ -1091,9 +1069,7 @@
              * dropped.
              */
             private void destroy() {
-                if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
-
-                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid + ": destroy"));
+                mEventLogger.enqueue(new EventLogger.StringEvent(mPuuid + ": destroy"));
 
                 synchronized (mRemoteServiceLock) {
                     disconnectLocked();
@@ -1127,7 +1103,7 @@
                                 Slog.e(TAG, mPuuid + ": Could not stop operation "
                                         + mRunningOpIds.valueAt(i), e);
 
-                                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
+                                mEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                         + ": Could not stop operation " + mRunningOpIds.valueAt(
                                         i)));
 
@@ -1157,7 +1133,7 @@
                     if (ri == null) {
                         Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
 
-                        sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
+                        mEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                 + ": " + mServiceName + " not found"));
 
                         return;
@@ -1168,7 +1144,7 @@
                         Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
                                 + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
 
-                        sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
+                        mEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                 + ": " + mServiceName + " does not require "
                                 + BIND_SOUND_TRIGGER_DETECTION_SERVICE));
 
@@ -1184,7 +1160,7 @@
                     } else {
                         Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
 
-                        sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
+                        mEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                 + ": Could not bind to " + mServiceName));
 
                     }
@@ -1206,7 +1182,7 @@
                                 mPuuid + ": Dropped operation as already destroyed or marked for "
                                         + "destruction");
 
-                        sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
+                        mEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                 + ":Dropped operation as already destroyed or marked for "
                                 + "destruction"));
 
@@ -1238,7 +1214,7 @@
                                             mPuuid + ": Dropped operation as too many operations "
                                                     + "were run in last 24 hours");
 
-                                    sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
+                                    mEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                             + ": Dropped operation as too many operations "
                                             + "were run in last 24 hours"));
 
@@ -1248,7 +1224,7 @@
                             } catch (Exception e) {
                                 Slog.e(TAG, mPuuid + ": Could not drop operation", e);
 
-                                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
+                                mEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                         + ": Could not drop operation"));
 
                             }
@@ -1265,7 +1241,7 @@
                             try {
                                 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
 
-                                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
+                                mEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                         + ": runOp " + opId));
 
                                 op.run(opId, mService);
@@ -1273,7 +1249,7 @@
                             } catch (Exception e) {
                                 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
 
-                                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
+                                mEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                         + ": Could not run operation " + opId));
 
                             }
@@ -1303,11 +1279,6 @@
 
             @Override
             public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
-                Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
-                        + ")");
-
-                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid + "->" + mServiceName
-                        + ": IGNORED onKeyphraseDetected(" + event + ")"));
             }
 
             /**
@@ -1325,7 +1296,7 @@
 
                 AudioFormat originalFormat = event.getCaptureFormat();
 
-                sEventLogger.enqueue(new EventLogger.StringEvent("createAudioRecordForEvent"));
+                mEventLogger.enqueue(new EventLogger.StringEvent("createAudioRecordForEvent"));
 
                 return (new AudioRecord.Builder())
                             .setAudioAttributes(attributes)
@@ -1340,11 +1311,6 @@
 
             @Override
             public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
-                if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
-
-                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
-                        + ": Generic sound trigger event: " + event));
-
                 runOrAddOperation(new Operation(
                         // always execute:
                         () -> {
@@ -1376,7 +1342,7 @@
             private void onError(int status) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
 
-                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
+                mEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + ": onError: " + status));
 
                 runOrAddOperation(
@@ -1421,27 +1387,17 @@
 
             @Override
             public void onRecognitionPaused() {
-                Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
-
-                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
-                        + "->" + mServiceName + ": IGNORED onRecognitionPaused"));
-
             }
 
             @Override
             public void onRecognitionResumed() {
-                Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
-
-                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
-                        + "->" + mServiceName + ": IGNORED onRecognitionResumed"));
-
             }
 
             @Override
             public void onServiceConnected(ComponentName name, IBinder service) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
 
-                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
+                mEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + ": onServiceConnected(" + service + ")"));
 
                 synchronized (mRemoteServiceLock) {
@@ -1464,7 +1420,7 @@
             public void onServiceDisconnected(ComponentName name) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
 
-                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
+                mEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + ": onServiceDisconnected"));
 
                 synchronized (mRemoteServiceLock) {
@@ -1476,7 +1432,7 @@
             public void onBindingDied(ComponentName name) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
 
-                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
+                mEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + ": onBindingDied"));
 
                 synchronized (mRemoteServiceLock) {
@@ -1488,7 +1444,7 @@
             public void onNullBinding(ComponentName name) {
                 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
 
-                sEventLogger.enqueue(new EventLogger.StringEvent(name + " for model "
+                mEventLogger.enqueue(new EventLogger.StringEvent(name + " for model "
                         + mPuuid + " returned a null binding"));
 
                 synchronized (mRemoteServiceLock) {
@@ -1613,17 +1569,25 @@
         private class SessionImpl implements Session {
             private final @NonNull SoundTriggerHelper mSoundTriggerHelper;
             private final @NonNull IBinder mClient;
+            private final EventLogger mEventLogger;
+            private final Identity mOriginatorIdentity;
 
-            private SessionImpl(
-                    @NonNull SoundTriggerHelper soundTriggerHelper, @NonNull IBinder client) {
+            private final SparseArray<UUID> mModelUuid = new SparseArray<>(1);
+
+            private SessionImpl(@NonNull SoundTriggerHelper soundTriggerHelper,
+                    @NonNull IBinder client,
+                    @NonNull EventLogger eventLogger, @NonNull Identity originatorIdentity) {
+
                 mSoundTriggerHelper = soundTriggerHelper;
                 mClient = client;
+                mOriginatorIdentity = originatorIdentity;
+                mEventLogger = eventLogger;
+
+                mSessionEventLoggers.add(mEventLogger);
                 try {
-                    mClient.linkToDeath(() -> {
-                        clientDied();
-                    }, 0);
+                    mClient.linkToDeath(() -> clientDied(), 0);
                 } catch (RemoteException e) {
-                    Slog.e(TAG, "Failed to register death listener.", e);
+                    clientDied();
                 }
             }
 
@@ -1631,6 +1595,9 @@
             public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
                     IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig,
                     boolean runInBatterySaverMode) {
+                mModelUuid.put(keyphraseId, soundModel.getUuid());
+                mEventLogger.enqueue(new SessionEvent(Type.START_RECOGNITION,
+                            soundModel.getUuid()));
                 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel,
                         listener, recognitionConfig, runInBatterySaverMode);
             }
@@ -1638,16 +1605,21 @@
             @Override
             public synchronized int stopRecognition(int keyphraseId,
                     IRecognitionStatusCallback listener) {
+                var uuid = mModelUuid.get(keyphraseId);
+                mEventLogger.enqueue(new SessionEvent(Type.STOP_RECOGNITION, uuid));
                 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
             }
 
             @Override
             public ModuleProperties getModuleProperties() {
+                mEventLogger.enqueue(new SessionEvent(Type.GET_MODULE_PROPERTIES, null));
                 return mSoundTriggerHelper.getModuleProperties();
             }
 
             @Override
             public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) {
+                var uuid = mModelUuid.get(keyphraseId);
+                mEventLogger.enqueue(new SessionEvent(Type.SET_PARAMETER, uuid));
                 return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value);
             }
 
@@ -1663,53 +1635,55 @@
             }
 
             @Override
-            public int unloadKeyphraseModel(int keyphraseId) {
-                return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
+            public void detach() {
+                detachInternal();
             }
 
             @Override
-            public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-                mSoundTriggerHelper.dump(fd, pw, args);
+            public int unloadKeyphraseModel(int keyphraseId) {
+                var uuid = mModelUuid.get(keyphraseId);
+                mEventLogger.enqueue(new SessionEvent(Type.UNLOAD_MODEL, uuid));
+                return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
             }
 
             private void clientDied() {
-                Slog.w(TAG, "Client died, cleaning up session.");
-                sEventLogger.enqueue(new EventLogger.StringEvent(
-                        "Client died, cleaning up session."));
+                mServiceEventLogger.enqueue(new ServiceEvent(
+                            ServiceEvent.Type.DETACH, mOriginatorIdentity.packageName,
+                            "Client died")
+                        .printLog(ALOGW, TAG));
+                detachInternal();
+            }
+
+            private void detachInternal() {
+                mEventLogger.enqueue(new SessionEvent(Type.DETACH, null));
+                mSessionEventLoggers.remove(mEventLogger);
+                addDetachedSessionLogger(mEventLogger);
                 mSoundTriggerHelper.detach();
             }
         }
 
         @Override
         public Session attach(@NonNull IBinder client, ModuleProperties underlyingModule) {
-            return new SessionImpl(newSoundTriggerHelper(underlyingModule), client);
+            var identity = IdentityContext.getNonNull();
+            int sessionId = mSessionIdCounter.getAndIncrement();
+            mServiceEventLogger.enqueue(new ServiceEvent(
+                        ServiceEvent.Type.ATTACH, identity.packageName + "#" + sessionId));
+            var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE,
+                    "LocalSoundTriggerEventLogger for package: " +
+                    identity.packageName + "#" + sessionId);
+
+            return new SessionImpl(newSoundTriggerHelper(underlyingModule, eventLogger),
+                    client, eventLogger, identity);
         }
 
         @Override
         public List<ModuleProperties> listModuleProperties(Identity originatorIdentity) {
+            mServiceEventLogger.enqueue(new ServiceEvent(
+                    ServiceEvent.Type.LIST_MODULE, originatorIdentity.packageName));
             try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
                     originatorIdentity)) {
                 return listUnderlyingModuleProperties(originatorIdentity);
             }
         }
-
-        @Override
-        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            // log
-            sEventLogger.dump(pw);
-
-            // enrolled models
-            mDbHelper.dump(pw);
-
-            // stats
-            mSoundModelStatTracker.dump(pw);
-        }
     }
-
-    //=================================================================
-    // For logging
-
-    private static final EventLogger sEventLogger = new EventLogger(200,
-            "SoundTrigger activity");
-
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java
index fba0fb4..cbc959c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java
@@ -45,6 +45,16 @@
     }
 
     /**
+     * Same as {@link #print(StringBuilder, Object, int)} with default max length.
+     *
+     * @param builder             StringBuilder to print into.
+     * @param obj                 The object to print.
+     */
+    static void print(@NonNull StringBuilder builder, @Nullable Object obj) {
+        print(builder, obj, kDefaultMaxCollectionLength);
+    }
+
+    /**
      * A version of {@link #print(Object, int)} that uses a {@link StringBuilder}.
      *
      * @param builder             StringBuilder to print into.
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index 4c134af..0e796d1 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -16,6 +16,10 @@
 
 package com.android.server.soundtrigger_middleware;
 
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.*;
+import static com.android.server.utils.EventLogger.Event.ALOGI;
+import static com.android.server.utils.EventLogger.Event.ALOGW;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -41,13 +45,20 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.LatencyTracker;
 import com.android.server.LocalServices;
+import com.android.server.utils.EventLogger.Event;
+import com.android.server.utils.EventLogger;
+
 
 import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.LinkedList;
+import java.util.Arrays;
 import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Supplier;
+import java.util.Deque;
+
 
 /**
  * An ISoundTriggerMiddlewareService decorator, which adds logging of all API calls (and
@@ -74,9 +85,17 @@
  */
 public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInternal, Dumpable {
     private static final String TAG = "SoundTriggerMiddlewareLogging";
+    private static final int SESSION_MAX_EVENT_SIZE = 128;
     private final @NonNull ISoundTriggerMiddlewareInternal mDelegate;
     private final @NonNull LatencyTracker mLatencyTracker;
     private final @NonNull Supplier<BatteryStatsInternal> mBatteryStatsInternalSupplier;
+    private final @NonNull EventLogger mServiceEventLogger = new EventLogger(256,
+            "Service Events");
+
+    private final Set<EventLogger> mSessionEventLoggers = ConcurrentHashMap.newKeySet(4);
+    private final Deque<EventLogger> mDetachedSessionEventLoggers = new LinkedBlockingDeque<>(4);
+    private final AtomicInteger mSessionCount = new AtomicInteger(0);
+
 
     public SoundTriggerMiddlewareLogging(@NonNull Context context,
             @NonNull ISoundTriggerMiddlewareInternal delegate) {
@@ -99,10 +118,19 @@
     SoundTriggerModuleDescriptor[] listModules() {
         try {
             SoundTriggerModuleDescriptor[] result = mDelegate.listModules();
-            logReturn("listModules", result);
+            var moduleSummary = Arrays.stream(result).map((descriptor) ->
+                    new ModulePropertySummary(descriptor.handle,
+                        descriptor.properties.implementor,
+                        descriptor.properties.version)).toArray(ModulePropertySummary[]::new);
+
+            mServiceEventLogger.enqueue(ServiceEvent.createForReturn(
+                        ServiceEvent.Type.LIST_MODULE,
+                        IdentityContext.get().packageName, moduleSummary).printLog(ALOGI, TAG));
             return result;
         } catch (Exception e) {
-            logException("listModules", e);
+            mServiceEventLogger.enqueue(ServiceEvent.createForException(
+                        ServiceEvent.Type.LIST_MODULE,
+                        IdentityContext.get().packageName, e).printLog(ALOGW, TAG));
             throw e;
         }
     }
@@ -111,12 +139,29 @@
     public @NonNull
     ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback) {
         try {
-            ModuleLogging result = new ModuleLogging(callback);
-            result.attach(mDelegate.attach(handle, result.getCallbackWrapper()));
-            logReturn("attach", result, handle, callback);
+            var originatorIdentity = IdentityContext.getNonNull();
+            String packageIdentification = originatorIdentity.packageName
+                    + mSessionCount.getAndIncrement();
+            ModuleLogging result = new ModuleLogging();
+            var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE,
+                "Session logger for: " + packageIdentification);
+
+            var callbackWrapper = new CallbackLogging(callback, eventLogger, originatorIdentity);
+
+            result.attach(mDelegate.attach(handle, callbackWrapper), eventLogger);
+
+            mServiceEventLogger.enqueue(ServiceEvent.createForReturn(
+                        ServiceEvent.Type.ATTACH,
+                        packageIdentification, result, handle, callback)
+                    .printLog(ALOGI, TAG));
+
+            mSessionEventLoggers.add(eventLogger);
             return result;
         } catch (Exception e) {
-            logException("attach", e, handle, callback);
+            mServiceEventLogger.enqueue(ServiceEvent.createForException(
+                        ServiceEvent.Type.ATTACH,
+                        IdentityContext.get().packageName, e, handle, callback)
+                    .printLog(ALOGW, TAG));
             throw e;
         }
     }
@@ -127,44 +172,27 @@
         return mDelegate.toString();
     }
 
-    private void logException(String methodName, Exception ex, Object... args) {
-        logExceptionWithObject(this, IdentityContext.get(), methodName, ex, args);
-    }
-
-    private void logReturn(String methodName, Object retVal, Object... args) {
-        logReturnWithObject(this, IdentityContext.get(), methodName, retVal, args);
-    }
-
-    private void logVoidReturn(String methodName, Object... args) {
-        logVoidReturnWithObject(this, IdentityContext.get(), methodName, args);
-    }
-
     private class ModuleLogging implements ISoundTriggerModule {
         private ISoundTriggerModule mDelegate;
-        private final @NonNull CallbackLogging mCallbackWrapper;
-        private final @NonNull Identity mOriginatorIdentity;
+        private EventLogger mEventLogger;
 
-        ModuleLogging(@NonNull ISoundTriggerCallback callback) {
-            mCallbackWrapper = new CallbackLogging(callback);
-            mOriginatorIdentity = IdentityContext.getNonNull();
-        }
-
-        void attach(@NonNull ISoundTriggerModule delegate) {
+        void attach(@NonNull ISoundTriggerModule delegate, EventLogger eventLogger) {
             mDelegate = delegate;
-        }
-
-        ISoundTriggerCallback getCallbackWrapper() {
-            return mCallbackWrapper;
+            mEventLogger = eventLogger;
         }
 
         @Override
         public int loadModel(SoundModel model) throws RemoteException {
             try {
                 int result = mDelegate.loadModel(model);
-                logReturn("loadModel", result, model);
+                mEventLogger.enqueue(SessionEvent.createForReturn(
+                            LOAD_MODEL, result, model.uuid)
+                        .printLog(ALOGI, TAG));
                 return result;
             } catch (Exception e) {
-                logException("loadModel", e, model);
+                mEventLogger.enqueue(SessionEvent.createForReturn(
+                            LOAD_MODEL, e, model.uuid)
+                        .printLog(ALOGW, TAG));
                 throw e;
             }
         }
@@ -173,10 +201,14 @@
         public int loadPhraseModel(PhraseSoundModel model) throws RemoteException {
             try {
                 int result = mDelegate.loadPhraseModel(model);
-                logReturn("loadPhraseModel", result, model);
+                mEventLogger.enqueue(SessionEvent.createForReturn(
+                            LOAD_PHRASE_MODEL, result, model.common.uuid)
+                        .printLog(ALOGI, TAG));
                 return result;
             } catch (Exception e) {
-                logException("loadPhraseModel", e, model);
+                mEventLogger.enqueue(SessionEvent.createForException(
+                            LOAD_PHRASE_MODEL, e, model.common.uuid)
+                        .printLog(ALOGW, TAG));
                 throw e;
             }
         }
@@ -185,9 +217,13 @@
         public void unloadModel(int modelHandle) throws RemoteException {
             try {
                 mDelegate.unloadModel(modelHandle);
-                logVoidReturn("unloadModel", modelHandle);
+                mEventLogger.enqueue(SessionEvent.createForVoid(
+                            UNLOAD_MODEL, modelHandle)
+                        .printLog(ALOGI, TAG));
             } catch (Exception e) {
-                logException("unloadModel", e, modelHandle);
+                mEventLogger.enqueue(SessionEvent.createForException(
+                            UNLOAD_MODEL, e, modelHandle)
+                        .printLog(ALOGW, TAG));
                 throw e;
             }
         }
@@ -197,9 +233,13 @@
                 throws RemoteException {
             try {
                 mDelegate.startRecognition(modelHandle, config);
-                logVoidReturn("startRecognition", modelHandle, config);
+                mEventLogger.enqueue(SessionEvent.createForVoid(
+                            START_RECOGNITION, modelHandle, config)
+                        .printLog(ALOGI, TAG));
             } catch (Exception e) {
-                logException("startRecognition", e, modelHandle, config);
+                mEventLogger.enqueue(SessionEvent.createForException(
+                            START_RECOGNITION, e, modelHandle, config)
+                        .printLog(ALOGW, TAG));
                 throw e;
             }
         }
@@ -208,9 +248,13 @@
         public void stopRecognition(int modelHandle) throws RemoteException {
             try {
                 mDelegate.stopRecognition(modelHandle);
-                logVoidReturn("stopRecognition", modelHandle);
+                mEventLogger.enqueue(SessionEvent.createForVoid(
+                            STOP_RECOGNITION, modelHandle)
+                        .printLog(ALOGI, TAG));
             } catch (Exception e) {
-                logException("stopRecognition", e, modelHandle);
+                mEventLogger.enqueue(SessionEvent.createForException(
+                            STOP_RECOGNITION, e, modelHandle)
+                        .printLog(ALOGW, TAG));
                 throw e;
             }
         }
@@ -219,9 +263,13 @@
         public void forceRecognitionEvent(int modelHandle) throws RemoteException {
             try {
                 mDelegate.forceRecognitionEvent(modelHandle);
-                logVoidReturn("forceRecognitionEvent", modelHandle);
+                mEventLogger.enqueue(SessionEvent.createForVoid(
+                            FORCE_RECOGNITION, modelHandle)
+                        .printLog(ALOGI, TAG));
             } catch (Exception e) {
-                logException("forceRecognitionEvent", e, modelHandle);
+                mEventLogger.enqueue(SessionEvent.createForException(
+                            FORCE_RECOGNITION, e, modelHandle)
+                        .printLog(ALOGW, TAG));
                 throw e;
             }
         }
@@ -231,9 +279,13 @@
                 throws RemoteException {
             try {
                 mDelegate.setModelParameter(modelHandle, modelParam, value);
-                logVoidReturn("setModelParameter", modelHandle, modelParam, value);
+                mEventLogger.enqueue(SessionEvent.createForVoid(
+                            SET_MODEL_PARAMETER, modelHandle, modelParam, value)
+                        .printLog(ALOGI, TAG));
             } catch (Exception e) {
-                logException("setModelParameter", e, modelHandle, modelParam, value);
+                mEventLogger.enqueue(SessionEvent.createForException(
+                            SET_MODEL_PARAMETER, e, modelHandle, modelParam, value)
+                        .printLog(ALOGW, TAG));
                 throw e;
             }
         }
@@ -242,10 +294,14 @@
         public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
             try {
                 int result = mDelegate.getModelParameter(modelHandle, modelParam);
-                logReturn("getModelParameter", result, modelHandle, modelParam);
+                mEventLogger.enqueue(SessionEvent.createForReturn(
+                            GET_MODEL_PARAMETER, result, modelHandle, modelParam)
+                        .printLog(ALOGI, TAG));
                 return result;
             } catch (Exception e) {
-                logException("getModelParameter", e, modelHandle, modelParam);
+                mEventLogger.enqueue(SessionEvent.createForException(
+                            GET_MODEL_PARAMETER, e, modelHandle, modelParam)
+                        .printLog(ALOGW, TAG));
                 throw e;
             }
         }
@@ -256,10 +312,14 @@
             try {
                 ModelParameterRange result = mDelegate.queryModelParameterSupport(modelHandle,
                         modelParam);
-                logReturn("queryModelParameterSupport", result, modelHandle, modelParam);
+                mEventLogger.enqueue(SessionEvent.createForReturn(
+                            QUERY_MODEL_PARAMETER, result, modelHandle, modelParam)
+                        .printLog(ALOGI, TAG));
                 return result;
             } catch (Exception e) {
-                logException("queryModelParameterSupport", e, modelHandle, modelParam);
+                mEventLogger.enqueue(SessionEvent.createForException(
+                            QUERY_MODEL_PARAMETER, e, modelHandle, modelParam)
+                        .printLog(ALOGW, TAG));
                 throw e;
             }
         }
@@ -267,10 +327,20 @@
         @Override
         public void detach() throws RemoteException {
             try {
+                if (mSessionEventLoggers.remove(mEventLogger)) {
+                    while (!mDetachedSessionEventLoggers.offerFirst(mEventLogger)) {
+                        // Remove the oldest element, if one still exists
+                        mDetachedSessionEventLoggers.pollLast();
+                    }
+                }
                 mDelegate.detach();
-                logVoidReturn("detach");
+                mEventLogger.enqueue(SessionEvent.createForVoid(
+                            DETACH)
+                        .printLog(ALOGI, TAG));
             } catch (Exception e) {
-                logException("detach", e);
+                mEventLogger.enqueue(SessionEvent.createForException(
+                            DETACH, e)
+                        .printLog(ALOGW, TAG));
                 throw e;
             }
         }
@@ -285,107 +355,112 @@
         public String toString() {
             return Objects.toString(mDelegate);
         }
+    }
 
-        private void logException(String methodName, Exception ex, Object... args) {
-            logExceptionWithObject(this, mOriginatorIdentity, methodName, ex, args);
+    private class CallbackLogging implements ISoundTriggerCallback {
+        private final ISoundTriggerCallback mCallbackDelegate;
+        private final EventLogger mEventLogger;
+        private final Identity mOriginatorIdentity;
+
+        private CallbackLogging(ISoundTriggerCallback delegate,
+                EventLogger eventLogger, Identity originatorIdentity) {
+            mCallbackDelegate = Objects.requireNonNull(delegate);
+            mEventLogger = Objects.requireNonNull(eventLogger);
+            mOriginatorIdentity = originatorIdentity;
         }
 
-        private void logReturn(String methodName, Object retVal, Object... args) {
-            logReturnWithObject(this, mOriginatorIdentity, methodName, retVal, args);
+        @Override
+        public void onRecognition(int modelHandle, RecognitionEvent event, int captureSession)
+                throws RemoteException {
+            try {
+                mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger(
+                        SystemClock.elapsedRealtime(), mOriginatorIdentity.uid);
+                mCallbackDelegate.onRecognition(modelHandle, event, captureSession);
+                mEventLogger.enqueue(SessionEvent.createForVoid(
+                            RECOGNITION, modelHandle, event, captureSession)
+                        .printLog(ALOGI, TAG));
+            } catch (Exception e) {
+                mEventLogger.enqueue(SessionEvent.createForException(
+                            RECOGNITION, e, modelHandle, event, captureSession)
+                        .printLog(ALOGW, TAG));
+                throw e;
+            }
         }
 
-        private void logVoidReturn(String methodName, Object... args) {
-            logVoidReturnWithObject(this, mOriginatorIdentity, methodName, args);
+        @Override
+        public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event,
+                int captureSession)
+                throws RemoteException {
+            try {
+                mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger(
+                        SystemClock.elapsedRealtime(), mOriginatorIdentity.uid);
+                startKeyphraseEventLatencyTracking(event);
+                mCallbackDelegate.onPhraseRecognition(modelHandle, event, captureSession);
+                mEventLogger.enqueue(SessionEvent.createForVoid(
+                            RECOGNITION, modelHandle, event, captureSession)
+                        .printLog(ALOGI, TAG));
+            } catch (Exception e) {
+                mEventLogger.enqueue(SessionEvent.createForException(
+                            RECOGNITION, e, modelHandle, event, captureSession)
+                        .printLog(ALOGW, TAG));
+                throw e;
+            }
         }
 
-        private class CallbackLogging implements ISoundTriggerCallback {
-            private final ISoundTriggerCallback mCallbackDelegate;
-
-            private CallbackLogging(ISoundTriggerCallback delegate) {
-                mCallbackDelegate = delegate;
+        @Override
+        public void onModelUnloaded(int modelHandle) throws RemoteException {
+            try {
+                mCallbackDelegate.onModelUnloaded(modelHandle);
+                mEventLogger.enqueue(SessionEvent.createForVoid(
+                            MODEL_UNLOADED, modelHandle)
+                        .printLog(ALOGI, TAG));
+            } catch (Exception e) {
+                mEventLogger.enqueue(SessionEvent.createForException(
+                            MODEL_UNLOADED, e, modelHandle)
+                        .printLog(ALOGW, TAG));
+                throw e;
             }
+        }
 
-            @Override
-            public void onRecognition(int modelHandle, RecognitionEvent event, int captureSession)
-                    throws RemoteException {
-                try {
-                    mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger(
-                            SystemClock.elapsedRealtime(), mOriginatorIdentity.uid);
-                    mCallbackDelegate.onRecognition(modelHandle, event, captureSession);
-                    logVoidReturn("onRecognition", modelHandle, event);
-                } catch (Exception e) {
-                    logException("onRecognition", e, modelHandle, event);
-                    throw e;
-                }
+        @Override
+        public void onResourcesAvailable() throws RemoteException {
+            try {
+                mCallbackDelegate.onResourcesAvailable();
+                mEventLogger.enqueue(SessionEvent.createForVoid(
+                            RESOURCES_AVAILABLE)
+                        .printLog(ALOGI, TAG));
+            } catch (Exception e) {
+                mEventLogger.enqueue(SessionEvent.createForException(
+                            RESOURCES_AVAILABLE, e)
+                        .printLog(ALOGW, TAG));
+                throw e;
             }
+        }
 
-            @Override
-            public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event,
-                    int captureSession)
-                    throws RemoteException {
-                try {
-                    mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger(
-                            SystemClock.elapsedRealtime(), mOriginatorIdentity.uid);
-                    startKeyphraseEventLatencyTracking(event);
-                    mCallbackDelegate.onPhraseRecognition(modelHandle, event, captureSession);
-                    logVoidReturn("onPhraseRecognition", modelHandle, event);
-                } catch (Exception e) {
-                    logException("onPhraseRecognition", e, modelHandle, event);
-                    throw e;
-                }
+        @Override
+        public void onModuleDied() throws RemoteException {
+            try {
+                mCallbackDelegate.onModuleDied();
+                mEventLogger.enqueue(SessionEvent.createForVoid(
+                            MODULE_DIED)
+                        .printLog(ALOGW, TAG));
+            } catch (Exception e) {
+                mEventLogger.enqueue(SessionEvent.createForException(
+                            MODULE_DIED, e)
+                        .printLog(ALOGW, TAG));
+                throw e;
             }
+        }
 
-            @Override
-            public void onModelUnloaded(int modelHandle) throws RemoteException {
-                try {
-                    mCallbackDelegate.onModelUnloaded(modelHandle);
-                    logVoidReturn("onModelUnloaded", modelHandle);
-                } catch (Exception e) {
-                    logException("onModelUnloaded", e, modelHandle);
-                    throw e;
-                }
-            }
+        @Override
+        public IBinder asBinder() {
+            return mCallbackDelegate.asBinder();
+        }
 
-            @Override
-            public void onResourcesAvailable() throws RemoteException {
-                try {
-                    mCallbackDelegate.onResourcesAvailable();
-                    logVoidReturn("onResourcesAvailable");
-                } catch (Exception e) {
-                    logException("onResourcesAvailable", e);
-                    throw e;
-                }
-            }
-
-            @Override
-            public void onModuleDied() throws RemoteException {
-                try {
-                    mCallbackDelegate.onModuleDied();
-                    logVoidReturn("onModuleDied");
-                } catch (Exception e) {
-                    logException("onModuleDied", e);
-                    throw e;
-                }
-            }
-
-            private void logException(String methodName, Exception ex, Object... args) {
-                logExceptionWithObject(this, mOriginatorIdentity, methodName, ex, args);
-            }
-
-            private void logVoidReturn(String methodName, Object... args) {
-                logVoidReturnWithObject(this, mOriginatorIdentity, methodName, args);
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return mCallbackDelegate.asBinder();
-            }
-
-            // Override toString() in order to have the delegate's ID in it.
-            @Override
-            public String toString() {
-                return Objects.toString(mCallbackDelegate);
-            }
+        // Override toString() in order to have the delegate's ID in it.
+        @Override
+        public String toString() {
+            return Objects.toString(mCallbackDelegate);
         }
     }
 
@@ -418,105 +493,193 @@
                 latencyTrackerTag);
     }
 
-    ////////////////////////////////////////////////////////////////////////////////////////////////
-    // Actual logging logic below.
-    private static final int NUM_EVENTS_TO_DUMP = 64;
-    private final static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss:SSS");
-    private final @NonNull LinkedList<Event> mLastEvents = new LinkedList<>();
-
-    static private class Event {
-        public final long timestamp = System.currentTimeMillis();
-        public final String message;
-
-        private Event(String message) {
-            this.message = message;
-        }
-    }
-
-    private static String printArgs(@NonNull Object[] args) {
-        StringBuilder result = new StringBuilder();
+    private static StringBuilder printArgs(StringBuilder builder, @NonNull Object[] args) {
         for (int i = 0; i < args.length; ++i) {
             if (i > 0) {
-                result.append(", ");
+                builder.append(", ");
             }
-            printObject(result, args[i]);
+            ObjectPrinter.print(builder, args[i]);
         }
-        return result.toString();
-    }
-
-    private static void printObject(@NonNull StringBuilder builder, @Nullable Object obj) {
-        ObjectPrinter.print(builder, obj, 16);
-    }
-
-    private static String printObject(@Nullable Object obj) {
-        StringBuilder builder = new StringBuilder();
-        printObject(builder, obj);
-        return builder.toString();
-    }
-
-    private void logReturnWithObject(@NonNull Object object, @Nullable Identity originatorIdentity,
-            String methodName,
-            @Nullable Object retVal,
-            @NonNull Object[] args) {
-        final String message = String.format("%s[this=%s, client=%s](%s) -> %s", methodName,
-                object,
-                printObject(originatorIdentity),
-                printArgs(args),
-                printObject(retVal));
-        Slog.i(TAG, message);
-        appendMessage(message);
-    }
-
-    private void logVoidReturnWithObject(@NonNull Object object,
-            @Nullable Identity originatorIdentity, @NonNull String methodName,
-            @NonNull Object[] args) {
-        final String message = String.format("%s[this=%s, client=%s](%s)", methodName,
-                object,
-                printObject(originatorIdentity),
-                printArgs(args));
-        Slog.i(TAG, message);
-        appendMessage(message);
-    }
-
-    private void logExceptionWithObject(@NonNull Object object,
-            @Nullable Identity originatorIdentity, @NonNull String methodName,
-            @NonNull Exception ex,
-            Object[] args) {
-        final String message = String.format("%s[this=%s, client=%s](%s) threw", methodName,
-                object,
-                printObject(originatorIdentity),
-                printArgs(args));
-        Slog.e(TAG, message, ex);
-        appendMessage(message + " " + ex.toString());
-    }
-
-    private void appendMessage(@NonNull String message) {
-        Event event = new Event(message);
-        synchronized (mLastEvents) {
-            if (mLastEvents.size() > NUM_EVENTS_TO_DUMP) {
-                mLastEvents.remove();
-            }
-            mLastEvents.add(event);
-        }
+        return builder;
     }
 
     @Override
     public void dump(PrintWriter pw) {
-        pw.println();
-        pw.println("=========================================");
-        pw.println("Last events");
-        pw.println("=========================================");
-        synchronized (mLastEvents) {
-            for (Event event : mLastEvents) {
-                pw.print(DATE_FORMAT.format(new Date(event.timestamp)));
-                pw.print('\t');
-                pw.println(event.message);
-            }
+        // Event loggers
+        pw.println("##Service-Wide logs:");
+        mServiceEventLogger.dump(pw, /* indent = */ "  ");
+
+        pw.println("\n##Active Session dumps:\n");
+        for (var sessionLogger : mSessionEventLoggers) {
+            sessionLogger.dump(pw, /* indent= */ "  ");
+            pw.println("");
         }
-        pw.println();
+        pw.println("##Detached Session dumps:\n");
+        for (var sessionLogger : mDetachedSessionEventLoggers) {
+            sessionLogger.dump(pw, /* indent= */ "  ");
+            pw.println("");
+        }
 
         if (mDelegate instanceof Dumpable) {
             ((Dumpable) mDelegate).dump(pw);
         }
     }
+
+    public static void printSystemLog(int type, String tag, String message, Exception e) {
+        switch (type) {
+            case Event.ALOGI:
+                Slog.i(tag, message, e);
+                break;
+            case Event.ALOGE:
+                Slog.e(tag, message, e);
+                break;
+            case Event.ALOGW:
+                Slog.w(tag, message, e);
+                break;
+            case Event.ALOGV:
+            default:
+                Slog.v(tag, message, e);
+        }
+    }
+
+    public static class ServiceEvent extends Event {
+        private final Type mType;
+        private final String mPackageName;
+        private final Object mReturnValue;
+        private final Object[] mParams;
+        private final Exception mException;
+
+        public enum Type {
+            ATTACH,
+            LIST_MODULE,
+        }
+
+        public static ServiceEvent createForException(Type type, String packageName,
+                Exception exception, Object... params) {
+            return new ServiceEvent(exception, type, packageName, null, params);
+        }
+
+        public static ServiceEvent createForReturn(Type type, String packageName,
+                Object returnValue, Object... params) {
+            return new ServiceEvent(null , type, packageName, returnValue, params);
+        }
+
+        private ServiceEvent(Exception exception, Type type, String packageName, Object returnValue,
+                Object... params) {
+            mException = exception;
+            mType = type;
+            mPackageName = packageName;
+            mReturnValue = returnValue;
+            mParams = params;
+        }
+
+        @Override
+        public Event printLog(int type, String tag) {
+            printSystemLog(type, tag, eventToString(), mException);
+            return this;
+        }
+
+        @Override
+        public String eventToString() {
+            var sb = new StringBuilder(mType.name()).append(" [client= ");
+            ObjectPrinter.print(sb, mPackageName);
+            sb.append("] (");
+            printArgs(sb, mParams);
+            sb.append(") -> ");
+            if (mException != null) {
+                sb.append("ERROR: ");
+                ObjectPrinter.print(sb, mException);
+            } else {
+                ObjectPrinter.print(sb, mReturnValue);
+            }
+            return sb.toString();
+        }
+    }
+
+    public static class SessionEvent extends Event {
+        public enum Type {
+            LOAD_MODEL,
+            LOAD_PHRASE_MODEL,
+            START_RECOGNITION,
+            STOP_RECOGNITION,
+            FORCE_RECOGNITION,
+            UNLOAD_MODEL,
+            GET_MODEL_PARAMETER,
+            SET_MODEL_PARAMETER,
+            QUERY_MODEL_PARAMETER,
+            DETACH,
+            RECOGNITION,
+            MODEL_UNLOADED,
+            MODULE_DIED,
+            RESOURCES_AVAILABLE,
+        }
+
+        private final Type mType;
+        private final Exception mException;
+        private final Object mReturnValue;
+        private final Object[] mParams;
+
+        public static SessionEvent createForException(Type type, Exception exception,
+                Object... params) {
+            return new SessionEvent(exception, type, null, params);
+        }
+
+        public static SessionEvent createForReturn(Type type,
+                Object returnValue, Object... params) {
+            return new SessionEvent(null , type, returnValue, params);
+        }
+
+        public static SessionEvent createForVoid(Type type, Object... params) {
+            return new SessionEvent(null, type, null, params);
+        }
+
+
+        private SessionEvent(Exception exception, Type type, Object returnValue,
+                Object... params) {
+            mException = exception;
+            mType = type;
+            mReturnValue = returnValue;
+            mParams = params;
+        }
+
+        @Override
+        public Event printLog(int type, String tag) {
+            printSystemLog(type, tag, eventToString(), mException);
+            return this;
+        }
+
+        @Override
+        public String eventToString() {
+            var sb = new StringBuilder(mType.name());
+            sb.append(" (");
+            printArgs(sb, mParams);
+            sb.append(")");
+            if (mException != null) {
+                sb.append(" -> ERROR: ");
+                ObjectPrinter.print(sb, mException);
+            } else if (mReturnValue != null) {
+                sb.append(" -> ");
+                ObjectPrinter.print(sb, mReturnValue);
+            }
+            return sb.toString();
+        }
+    }
+
+    private static final class ModulePropertySummary {
+        private int mId;
+        private String mImplementor;
+        private int mVersion;
+
+        ModulePropertySummary(int id, String implementor, int version) {
+            mId = id;
+            mImplementor = implementor;
+            mVersion = version;
+        }
+
+        @Override
+       public String toString() {
+           return "{Id: " + mId + ", Implementor: " + mImplementor
+               + ", Version: " + mVersion + "}";
+       }
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java
index 2413072..65c95d1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java
@@ -339,7 +339,11 @@
             } catch (IOException e) {
                 mAudioSource.closeWithError(e.getMessage());
                 mAudioSink.closeWithError(e.getMessage());
-                Slog.e(TAG, mStreamTaskId + ": Failed to copy audio stream", e);
+                // This is expected when VIS closes the read side of the pipe on their end,
+                // so when the HotwordAudioStreamCopier tries to write, we will get that broken
+                // pipe error. HDS is also closing the write side of the pipe (the system is on the
+                // read end of that pipe).
+                Slog.i(TAG, mStreamTaskId + ": Failed to copy audio stream", e);
                 HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType,
                         HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__CLOSE_ERROR_FROM_SYSTEM,
                         mUid, /* streamSizeBytes= */ 0, /* bundleSizeBytes= */ 0,
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionBinderProxy.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionBinderProxy.java
index dd9fee3..0ef2f06 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionBinderProxy.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionBinderProxy.java
@@ -69,4 +69,9 @@
     public SoundTrigger.ModelParamRange queryParameter(int i, int i1) throws RemoteException {
         return mDelegate.queryParameter(i, i1);
     }
+
+    @Override
+    public void detach() throws RemoteException {
+        mDelegate.detach();
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java
index c0c3e6f..0f8a945 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java
@@ -113,6 +113,15 @@
                 "This object isn't intended to be used as a Binder.");
     }
 
+    @Override
+    public void detach() {
+        try {
+            mDelegate.detach();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     // TODO: Share this code with SoundTriggerMiddlewarePermission.
     private boolean isHoldingPermissions() {
         try {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 1d7b966..27f3fb3 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1856,6 +1856,11 @@
                         "This object isn't intended to be used as a Binder.");
             }
 
+            @Override
+            public void detach() {
+                mSession.detach();
+            }
+
             private int unloadKeyphraseModel(int keyphraseId) {
                 final long caller = Binder.clearCallingIdentity();
                 try {
@@ -2133,8 +2138,6 @@
                     mImpl.dumpLocked(fd, pw, args);
                 }
             }
-
-            mSoundTriggerInternal.dump(fd, pw, args);
         }
 
         @Override
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 282b64d..18e4c37 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3008,4 +3008,14 @@
      * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
      */
     boolean setSatelliteListeningTimeoutDuration(in long timeoutMillis);
+
+    /**
+     * This API can be used by only CTS to update satellite pointing UI app package and class names.
+     *
+     * @param packageName The package name of the satellite pointing UI app.
+     * @param className The class name of the satellite pointing UI app.
+     * @return {@code true} if the satellite pointing UI app package and class is set successfully,
+     * {@code false} otherwise.
+     */
+    boolean setSatellitePointingUiClassName(in String packageName, in String className);
 }