Merge "Provide color override path through keyguard preview renderer" into udc-dev
diff --git a/core/java/android/app/IUidObserver.aidl b/core/java/android/app/IUidObserver.aidl
index 0c920f1..60c2eed 100644
--- a/core/java/android/app/IUidObserver.aidl
+++ b/core/java/android/app/IUidObserver.aidl
@@ -58,8 +58,9 @@
      * Report a proc oom adj change associated with a uid.
      *
      * @param uid The uid for which the state change is being reported.
+     * @param adj The minimum OOM adj among all processes with this uid.
      */
-    void onUidProcAdjChanged(int uid);
+    void onUidProcAdjChanged(int uid, int adj);
 
     // =============== End of transactions used on native side as well ============================
 
diff --git a/core/java/android/app/UidObserver.java b/core/java/android/app/UidObserver.java
index 9e92807..5196624 100644
--- a/core/java/android/app/UidObserver.java
+++ b/core/java/android/app/UidObserver.java
@@ -41,7 +41,7 @@
     }
 
     @Override
-    public void onUidProcAdjChanged(int uid) {
+    public void onUidProcAdjChanged(int uid, int adj) {
     }
 
     @Override
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 6592019..f673304 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -822,6 +822,10 @@
      */
     @TestApi
     public boolean isLockscreenLiveWallpaperEnabled() {
+        return isLockscreenLiveWallpaperEnabledHelper();
+    }
+
+    private static boolean isLockscreenLiveWallpaperEnabledHelper() {
         if (sGlobals == null) {
             sIsLockscreenLiveWallpaperEnabled = SystemProperties.getBoolean(
                     "persist.wm.debug.lockscreen_live_wallpaper", false);
@@ -2757,7 +2761,7 @@
     public static InputStream openDefaultWallpaper(Context context, @SetWallpaperFlags int which) {
         final String whichProp;
         final int defaultResId;
-        if (which == FLAG_LOCK && !sIsLockscreenLiveWallpaperEnabled) {
+        if (which == FLAG_LOCK && !isLockscreenLiveWallpaperEnabledHelper()) {
             /* Factory-default lock wallpapers are not yet supported
             whichProp = PROP_LOCK_WALLPAPER;
             defaultResId = com.android.internal.R.drawable.default_lock_wallpaper;
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 1ba84c5..d802b46 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -502,6 +502,8 @@
 
     boolean hasSystemFeature(String name, int version);
 
+    List<String> getInitialNonStoppedSystemPackages();
+
     void enterSafeMode();
     @UnsupportedAppUsage
     boolean isSafeMode();
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 295df5c..be40143 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -50,7 +50,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
-import java.lang.IllegalArgumentException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -285,6 +284,12 @@
      */
     public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103;
 
+    /**
+     * The maximum length of Shortcut ID. IDs will be truncated at this limit.
+     * @hide
+     */
+    public static final int MAX_ID_LENGTH = 1000;
+
     /** @hide */
     @IntDef(prefix = { "DISABLED_REASON_" }, value = {
             DISABLED_REASON_NOT_DISABLED,
@@ -477,8 +482,7 @@
 
     private ShortcutInfo(Builder b) {
         mUserId = b.mContext.getUserId();
-
-        mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided");
+        mId = getSafeId(Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"));
 
         // Note we can't do other null checks here because SM.updateShortcuts() takes partial
         // information.
@@ -584,6 +588,14 @@
         return ret;
     }
 
+    @NonNull
+    private static String getSafeId(@NonNull String id) {
+        if (id.length() > MAX_ID_LENGTH) {
+            return id.substring(0, MAX_ID_LENGTH);
+        }
+        return id;
+    }
+
     /**
      * Throws if any of the mandatory fields is not set.
      *
@@ -2342,7 +2354,8 @@
         final ClassLoader cl = getClass().getClassLoader();
 
         mUserId = source.readInt();
-        mId = source.readString8();
+        mId = getSafeId(Preconditions.checkStringNotEmpty(source.readString8(),
+                "Shortcut ID must be provided"));
         mPackageName = source.readString8();
         mActivity = source.readParcelable(cl, android.content.ComponentName.class);
         mFlags = source.readInt();
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 888047d..21fe686 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -32,10 +32,12 @@
 import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionMode;
 import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.os.ParcelFileDescriptor;
-import android.system.ErrnoException;
 import android.os.SharedMemory;
+import android.system.ErrnoException;
 
 import java.nio.ByteBuffer;
 import java.util.Arrays;
@@ -219,36 +221,40 @@
         return new SoundTrigger.ConfidenceLevel(apiLevel.userId, apiLevel.levelPercent);
     }
 
-    public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent(
-            int modelHandle, int captureSession, RecognitionEvent aidlEvent) {
+    public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent(int modelHandle,
+            int captureSession, RecognitionEventSys aidlEvent) {
+        RecognitionEvent recognitionEvent = aidlEvent.recognitionEvent;
         // The API recognition event doesn't allow for a null audio format, even though it doesn't
         // always make sense. We thus replace it with a default.
-        AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(aidlEvent.audioConfig,
+        AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(recognitionEvent.audioConfig,
                 true /*isInput*/);
-        // TODO(b/265852186) propagate a timestamp from aidl interfaces
-        return new SoundTrigger.GenericRecognitionEvent(aidlEvent.status, modelHandle,
-                aidlEvent.captureAvailable, captureSession, aidlEvent.captureDelayMs,
-                aidlEvent.capturePreambleMs, aidlEvent.triggerInData, audioFormat, aidlEvent.data,
-                aidlEvent.recognitionStillActive, -1 /* halEventReceivedMillis */);
+        return new SoundTrigger.GenericRecognitionEvent(recognitionEvent.status, modelHandle,
+                recognitionEvent.captureAvailable, captureSession, recognitionEvent.captureDelayMs,
+                recognitionEvent.capturePreambleMs, recognitionEvent.triggerInData, audioFormat,
+                recognitionEvent.data,
+                recognitionEvent.recognitionStillActive, aidlEvent.halEventReceivedMillis);
     }
 
     public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent(
-            int modelHandle, int captureSession,
-            PhraseRecognitionEvent aidlEvent) {
+            int modelHandle, int captureSession, PhraseRecognitionEventSys aidlEvent) {
+        PhraseRecognitionEvent recognitionEvent = aidlEvent.phraseRecognitionEvent;
         SoundTrigger.KeyphraseRecognitionExtra[] apiExtras =
-                new SoundTrigger.KeyphraseRecognitionExtra[aidlEvent.phraseExtras.length];
-        for (int i = 0; i < aidlEvent.phraseExtras.length; ++i) {
-            apiExtras[i] = aidl2apiPhraseRecognitionExtra(aidlEvent.phraseExtras[i]);
+                new SoundTrigger.KeyphraseRecognitionExtra[recognitionEvent.phraseExtras.length];
+        for (int i = 0; i < recognitionEvent.phraseExtras.length; ++i) {
+            apiExtras[i] = aidl2apiPhraseRecognitionExtra(recognitionEvent.phraseExtras[i]);
         }
         // The API recognition event doesn't allow for a null audio format, even though it doesn't
         // always make sense. We thus replace it with a default.
-        AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(aidlEvent.common.audioConfig,
+        AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(
+                recognitionEvent.common.audioConfig,
                 true /*isInput*/);
-        // TODO(b/265852186) propagate a timestamp from aidl interfaces
-        return new SoundTrigger.KeyphraseRecognitionEvent(aidlEvent.common.status, modelHandle,
-                aidlEvent.common.captureAvailable, captureSession, aidlEvent.common.captureDelayMs,
-                aidlEvent.common.capturePreambleMs, aidlEvent.common.triggerInData, audioFormat,
-                aidlEvent.common.data, apiExtras, -1 /* halEventReceivedMillis */);
+        return new SoundTrigger.KeyphraseRecognitionEvent(recognitionEvent.common.status,
+                modelHandle,
+                recognitionEvent.common.captureAvailable, captureSession,
+                recognitionEvent.common.captureDelayMs,
+                recognitionEvent.common.capturePreambleMs, recognitionEvent.common.triggerInData,
+                audioFormat,
+                recognitionEvent.common.data, apiExtras, aidlEvent.halEventReceivedMillis);
     }
 
     // In case of a null input returns a non-null valid output.
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 37c5213..5cdbe23 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -22,13 +22,13 @@
 import android.media.permission.ClearCallingIdentityContext;
 import android.media.permission.Identity;
 import android.media.permission.SafeCloseable;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
@@ -398,7 +398,7 @@
         }
 
         @Override
-        public synchronized void onRecognition(int handle, RecognitionEvent event,
+        public synchronized void onRecognition(int handle, RecognitionEventSys event,
                 int captureSession)
                 throws RemoteException {
             Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
@@ -407,7 +407,7 @@
         }
 
         @Override
-        public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEvent event,
+        public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEventSys event,
                 int captureSession)
                 throws RemoteException {
             Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 24e28e9..5bcbaa1 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -65,7 +65,6 @@
 import android.view.WindowManager.LayoutParams;
 
 import com.android.internal.R;
-import com.android.internal.util.FrameworkStatsLog;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -2533,38 +2532,6 @@
     }
 
     /**
-     * Returns the enum defined in the statsd UserLifecycleJourneyReported atom corresponding to the
-     * user type.
-     * @hide
-     */
-    public static int getUserTypeForStatsd(@NonNull String userType) {
-        switch (userType) {
-            case USER_TYPE_FULL_SYSTEM:
-                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SYSTEM;
-            case USER_TYPE_FULL_SECONDARY:
-                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY;
-            case USER_TYPE_FULL_GUEST:
-                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_GUEST;
-            case USER_TYPE_FULL_DEMO:
-                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_DEMO;
-            case USER_TYPE_FULL_RESTRICTED:
-                return FrameworkStatsLog
-                        .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_RESTRICTED;
-            case USER_TYPE_PROFILE_MANAGED:
-                return FrameworkStatsLog
-                        .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_MANAGED;
-            case USER_TYPE_SYSTEM_HEADLESS:
-                return FrameworkStatsLog
-                        .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__SYSTEM_HEADLESS;
-            case USER_TYPE_PROFILE_CLONE:
-                return FrameworkStatsLog
-                        .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_CLONE;
-            default:
-                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
-        }
-    }
-
-    /**
      * @hide
      * @deprecated Use {@link #isRestrictedProfile()}
      */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4a46beb..329a2fa5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7175,6 +7175,13 @@
         public static final String CREDENTIAL_SERVICE = "credential_service";
 
         /**
+         * The currently selected primary credential service flattened ComponentName.
+         *
+         * @hide
+         */
+        public static final String CREDENTIAL_SERVICE_PRIMARY = "credential_service_primary";
+
+        /**
          * The currently selected autofill service flattened ComponentName.
          * @hide
          */
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index e39b3a1..6ff4b74 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1477,14 +1477,22 @@
         // to PCC classification service.
         if (AutofillFeatureFlags.isAutofillPccClassificationEnabled()) {
             synchronized (mLock) {
-                final boolean clientAdded = tryAddServiceClientIfNeededLocked();
-                if (clientAdded){
-                    startSessionLocked(/* id= */ AutofillId.NO_AUTOFILL_ID,
-                        /* bounds= */ null, /* value= */ null, /* flags= */ FLAG_PCC_DETECTION);
-                } else {
-                    if (sVerbose) {
-                        Log.v(TAG, "not starting session: no service client");
+                // If session has already been created, that'd mean we already have issued the
+                // detection request previously. It is possible in cases like autofocus that this
+                // method isn't invoked, so the server should still handle such cases where fill
+                // request comes in but PCC Detection hasn't been triggered. There is no benefit to
+                // trigger PCC Detection separately in those cases.
+                if (!isActiveLocked()) {
+                    final boolean clientAdded = tryAddServiceClientIfNeededLocked();
+                    if (clientAdded) {
+                        startSessionLocked(/* id= */ AutofillId.NO_AUTOFILL_ID, /* bounds= */ null,
+                                /* value= */ null, /* flags= */ FLAG_PCC_DETECTION);
+                    } else {
+                        if (sVerbose) {
+                            Log.v(TAG, "not starting session: no service client");
+                        }
                     }
+
                 }
             }
         }
diff --git a/core/proto/android/companion/telecom.proto b/core/proto/android/companion/telecom.proto
index 3a9e5ee..02ba7c5 100644
--- a/core/proto/android/companion/telecom.proto
+++ b/core/proto/android/companion/telecom.proto
@@ -25,7 +25,7 @@
   // Next index: 6
   message Call {
     // UUID representing this call
-    int64 id = 1;
+    string id = 1;
 
     message Origin {
       // Caller's name and/or phone number; what a user would see displayed when receiving an
@@ -48,22 +48,23 @@
     }
     Status status = 3;
 
-    enum Control {
-      UNKNOWN_CONTROL = 0;
-      ACCEPT = 1;
-      REJECT = 2;
-      SILENCE = 3;
-      MUTE = 4;
-      UNMUTE = 5;
-      END = 6;
-      PUT_ON_HOLD = 7;
-      TAKE_OFF_HOLD = 8;
-      REJECT_AND_BLOCK = 9;
-      IGNORE = 10;
-    }
     repeated Control controls = 4;
   }
 
+  enum Control {
+    UNKNOWN_CONTROL = 0;
+    ACCEPT = 1;
+    REJECT = 2;
+    SILENCE = 3;
+    MUTE = 4;
+    UNMUTE = 5;
+    END = 6;
+    PUT_ON_HOLD = 7;
+    TAKE_OFF_HOLD = 8;
+    REJECT_AND_BLOCK = 9;
+    IGNORE = 10;
+  }
+
   // The list of active calls.
   repeated Call calls = 1;
   // The list of requested calls or call changes.
diff --git a/core/res/res/drawable-hdpi/pointer_context_menu.png b/core/res/res/drawable-hdpi/pointer_context_menu.png
index c45d29b..60d37c4 100644
--- a/core/res/res/drawable-hdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-hdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_help.png b/core/res/res/drawable-hdpi/pointer_help.png
index a3afdb6..2d9d20c 100644
--- a/core/res/res/drawable-hdpi/pointer_help.png
+++ b/core/res/res/drawable-hdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_context_menu.png b/core/res/res/drawable-mdpi/pointer_context_menu.png
index e0e849d..d87d040 100644
--- a/core/res/res/drawable-mdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-mdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_context_menu_large.png b/core/res/res/drawable-mdpi/pointer_context_menu_large.png
index e8c9be4..15266a6 100644
--- a/core/res/res/drawable-mdpi/pointer_context_menu_large.png
+++ b/core/res/res/drawable-mdpi/pointer_context_menu_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_help.png b/core/res/res/drawable-mdpi/pointer_help.png
index 286242c..bd04bbd 100644
--- a/core/res/res/drawable-mdpi/pointer_help.png
+++ b/core/res/res/drawable-mdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_help_large.png b/core/res/res/drawable-mdpi/pointer_help_large.png
index 27f4a84..f9bd2b7 100644
--- a/core/res/res/drawable-mdpi/pointer_help_large.png
+++ b/core/res/res/drawable-mdpi/pointer_help_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_context_menu.png b/core/res/res/drawable-xhdpi/pointer_context_menu.png
index d4b2bde..15d1e33 100644
--- a/core/res/res/drawable-xhdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-xhdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_context_menu_large.png b/core/res/res/drawable-xhdpi/pointer_context_menu_large.png
index 977df10..e4ff7a6 100644
--- a/core/res/res/drawable-xhdpi/pointer_context_menu_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_context_menu_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_help.png b/core/res/res/drawable-xhdpi/pointer_help.png
index 5a6805c..952a4ee 100644
--- a/core/res/res/drawable-xhdpi/pointer_help.png
+++ b/core/res/res/drawable-xhdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_help_large.png b/core/res/res/drawable-xhdpi/pointer_help_large.png
index 4bdc3d1..1d68437 100644
--- a/core/res/res/drawable-xhdpi/pointer_help_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_help_large.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_context_menu.png b/core/res/res/drawable-xxhdpi/pointer_context_menu.png
index 6ebfaab..4cd20f5 100644
--- a/core/res/res/drawable-xxhdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-xxhdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_help.png b/core/res/res/drawable-xxhdpi/pointer_help.png
index 96b2a71..0c7a264 100644
--- a/core/res/res/drawable-xxhdpi/pointer_help.png
+++ b/core/res/res/drawable-xxhdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable/pointer_context_menu_large_icon.xml b/core/res/res/drawable/pointer_context_menu_large_icon.xml
index e07e5b6..325ea669 100644
--- a/core/res/res/drawable/pointer_context_menu_large_icon.xml
+++ b/core/res/res/drawable/pointer_context_menu_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_context_menu_large"
-    android:hotSpotX="13.5dp"
-    android:hotSpotY="10.5dp" />
+    android:hotSpotX="10.5dp"
+    android:hotSpotY="8dp" />
diff --git a/core/res/res/drawable/pointer_help_large_icon.xml b/core/res/res/drawable/pointer_help_large_icon.xml
index 43a1261..20f0c5336 100644
--- a/core/res/res/drawable/pointer_help_large_icon.xml
+++ b/core/res/res/drawable/pointer_help_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_help_large"
-    android:hotSpotX="13.5dp"
-    android:hotSpotY="10.5dp" />
+    android:hotSpotX="10.5dp"
+    android:hotSpotY="8dp" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7bc3ab8..50aec83 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -991,6 +991,25 @@
         <!-- Nominal White Z --> <item>1.089058</item>
     </string-array>
 
+    <!-- The CCT closest to the white coordinates (primary) above and in SurfaceControl. -->
+    <integer name="config_displayWhiteBalanceDisplayNominalWhiteCct">6500</integer>
+
+    <!-- Range minimums corresponding to config_displayWhiteBalanceDisplaySteps. For example, if the
+         range minimums are [0, 3000] and the steps are [10, 20] then between 0 and 3000, exclusive,
+         the step between them will be 10 (i.e. 0, 10, 20, etc.) and the step between 3000 and the
+         maximum value is 20 (i.e. 3000, 3020, 3040, etc.). -->
+    <integer-array name="config_displayWhiteBalanceDisplayRangeMinimums">
+        <item>0</item>
+    </integer-array>
+
+    <!-- Steps corresponding to config_displayWhiteBalanceDisplayRangeMinimums. For example, if the
+         range minimums are [0, 3000] and the steps are [10, 20] then between 0 and 3000, exclusive,
+         the step between them will be 10 (i.e. 0, 10, 20, etc.) and the step between 3000 and the
+         maximum value is 20 (i.e. 3000, 3020, 3040, etc.). -->
+    <integer-array name="config_displayWhiteBalanceDisplaySteps">
+        <item>1</item>
+    </integer-array>
+
     <!-- Boolean indicating whether light mode is allowed when DWB is turned on. -->
     <bool name="config_displayWhiteBalanceLightModeAllowed">true</bool>
 
@@ -6396,7 +6415,7 @@
         Packages can be added by OEMs in an allowlist, to prevent them from being scanned as
         "stopped" during initial boot of a device, or after an OTA update. Stopped state of
         an app is not changed during subsequent reboots.  -->
-    <bool name="config_stopSystemPackagesByDefault">false</bool>
+    <bool name="config_stopSystemPackagesByDefault">true</bool>
 
     <!-- Whether to show weather on the lock screen by default. -->
     <bool name="config_lockscreenWeatherEnabledByDefault">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e25425d..218bbc2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3423,6 +3423,9 @@
   <java-symbol type="integer" name="config_displayWhiteBalanceColorTemperatureDefault" />
   <java-symbol type="array" name="config_displayWhiteBalanceDisplayPrimaries" />
   <java-symbol type="array" name="config_displayWhiteBalanceDisplayNominalWhite" />
+  <java-symbol type="integer" name="config_displayWhiteBalanceDisplayNominalWhiteCct" />
+  <java-symbol type="array" name="config_displayWhiteBalanceDisplayRangeMinimums" />
+  <java-symbol type="array" name="config_displayWhiteBalanceDisplaySteps" />
   <java-symbol type="bool" name="config_displayWhiteBalanceLightModeAllowed" />
   <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTime" />
 
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 40cb7f2..1f1239e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -520,6 +520,10 @@
         <permission name="android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER"/>
         <!-- Permission required for CTS test - SatelliteManagerTest -->
         <permission name="android.permission.SATELLITE_COMMUNICATION"/>
+        <!-- Permission required for GTS test - GtsAttestationVerificationDeviceSideTestCases -->
+        <permission name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" />
+        <!-- Permission required for GTS test - GtsCredentialsTestCases -->
+        <permission name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 6a79bc1..54978bd 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -125,34 +125,6 @@
 
 // End ProtoLog
 
-gensrcs {
-    name: "wm-shell-protos",
-
-    tools: [
-        "aprotoc",
-        "protoc-gen-javastream",
-        "soong_zip",
-    ],
-
-    tool_files: [
-        ":libprotobuf-internal-protos",
-    ],
-
-    cmd: "mkdir -p $(genDir)/$(in) " +
-        "&& $(location aprotoc) " +
-        "  --plugin=$(location protoc-gen-javastream) " +
-        "  --javastream_out=$(genDir)/$(in) " +
-        "  -Iexternal/protobuf/src " +
-        "  -I . " +
-        "  $(in) " +
-        "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
-
-    srcs: [
-        "proto/**/*.proto",
-    ],
-    output_extension: "srcjar",
-}
-
 java_library {
     name: "WindowManager-Shell-proto",
 
@@ -170,7 +142,6 @@
         // TODO(b/168581922) protologtool do not support kotlin(*.kt)
         ":wm_shell-sources-kt",
         ":wm_shell-aidls",
-        ":wm-shell-protos",
     ],
     resource_dirs: [
         "res",
diff --git a/libs/WindowManager/Shell/res/color/taskbar_background.xml b/libs/WindowManager/Shell/res/color/taskbar_background.xml
deleted file mode 100644
index 876ee02..0000000
--- a/libs/WindowManager/Shell/res/color/taskbar_background.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 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.
-  -->
-<!-- Should be the same as in packages/apps/Launcher3/res/color-v31/taskbar_background.xml -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="98" />
-</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color-night/taskbar_background.xml b/libs/WindowManager/Shell/res/color/taskbar_background_dark.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/color-night/taskbar_background.xml
rename to libs/WindowManager/Shell/res/color/taskbar_background_dark.xml
diff --git a/libs/WindowManager/Shell/res/values-night/colors.xml b/libs/WindowManager/Shell/res/values-night/colors.xml
index 5c6bb57..83c4d93 100644
--- a/libs/WindowManager/Shell/res/values-night/colors.xml
+++ b/libs/WindowManager/Shell/res/values-night/colors.xml
@@ -15,7 +15,6 @@
   -->
 
 <resources>
-    <color name="docked_divider_handle">#ffffff</color>
     <!-- Bubbles -->
     <color name="bubbles_icon_tint">@color/GM2_grey_200</color>
     <!-- Splash screen-->
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index c487e4a..54a8f33 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -17,8 +17,8 @@
  */
 -->
 <resources>
-    <color name="docked_divider_handle">#000000</color>
-    <color name="split_divider_background">@color/taskbar_background</color>
+    <color name="docked_divider_handle">#ffffff</color>
+    <color name="split_divider_background">@color/taskbar_background_dark</color>
     <drawable name="forced_resizable_background">#59000000</drawable>
     <color name="minimize_dock_shadow_start">#60000000</color>
     <color name="minimize_dock_shadow_end">#00000000</color>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index f3efaad..e7ec7aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -3320,7 +3320,9 @@
      * @return the normalized x-axis position of the bubble stack rounded to 4 decimal places.
      */
     public float getNormalizedXPosition() {
-        return new BigDecimal(getStackPosition().x / mPositioner.getAvailableRect().width())
+        int width = mPositioner.getAvailableRect().width();
+        float stackPosition = width > 0 ? getStackPosition().x / width : 0;
+        return new BigDecimal(stackPosition)
                 .setScale(4, RoundingMode.CEILING.HALF_UP)
                 .floatValue();
     }
@@ -3329,7 +3331,9 @@
      * @return the normalized y-axis position of the bubble stack rounded to 4 decimal places.
      */
     public float getNormalizedYPosition() {
-        return new BigDecimal(getStackPosition().y / mPositioner.getAvailableRect().height())
+        int height = mPositioner.getAvailableRect().height();
+        float stackPosition = height > 0 ? getStackPosition().y / height : 0;
+        return new BigDecimal(stackPosition)
                 .setScale(4, RoundingMode.CEILING.HALF_UP)
                 .floatValue();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 80e920f..28368ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -189,12 +189,13 @@
     static Optional<DragAndDropController> provideDragAndDropController(Context context,
             ShellInit shellInit,
             ShellController shellController,
+            ShellCommandHandler shellCommandHandler,
             DisplayController displayController,
             UiEventLogger uiEventLogger,
             IconProvider iconProvider,
             @ShellMainThread ShellExecutor mainExecutor) {
         return Optional.ofNullable(DragAndDropController.create(context, shellInit, shellController,
-                displayController, uiEventLogger, iconProvider, mainExecutor));
+                shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor));
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index fb0a91f..0f0d572 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -162,7 +162,7 @@
     /**
      * Release the indicator and its components when it is no longer needed.
      */
-    public void releaseVisualIndicator() {
+    public void releaseVisualIndicator(SurfaceControl.Transaction t) {
         if (mViewHost == null) return;
         if (mViewHost != null) {
             mViewHost.release();
@@ -170,13 +170,8 @@
         }
 
         if (mLeash != null) {
-            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
             t.remove(mLeash);
             mLeash = null;
-            mSyncQueue.runInSync(transaction -> {
-                transaction.merge(t);
-                t.close();
-            });
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 73a0e36..ee94f30 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -78,6 +78,11 @@
 
     private val desktopMode: DesktopModeImpl
     private var visualIndicator: DesktopModeVisualIndicator? = null
+    private val mOnAnimationFinishedCallback = Consumer<SurfaceControl.Transaction> {
+        t: SurfaceControl.Transaction ->
+        visualIndicator?.releaseVisualIndicator(t)
+        visualIndicator = null
+    }
 
     init {
         desktopMode = DesktopModeImpl()
@@ -154,14 +159,14 @@
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             enterDesktopTaskTransitionHandler.startTransition(
-                    Transitions.TRANSIT_ENTER_FREEFORM, wct)
+                    Transitions.TRANSIT_ENTER_FREEFORM, wct, mOnAnimationFinishedCallback)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
         }
     }
 
     /** Brings apps to front and sets freeform task bounds */
-    fun moveToDesktopWithAnimation(
+    private fun moveToDesktopWithAnimation(
             taskInfo: RunningTaskInfo,
             freeformBounds: Rect
     ) {
@@ -172,9 +177,10 @@
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             enterDesktopTaskTransitionHandler.startTransition(
-                Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct)
+                Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct, mOnAnimationFinishedCallback)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
+            releaseVisualIndicator()
         }
     }
 
@@ -205,21 +211,24 @@
         val wct = WindowContainerTransaction()
         addMoveToFullscreenChanges(wct, task.token)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, startPosition)
+            enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, startPosition,
+                    mOnAnimationFinishedCallback)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
+            releaseVisualIndicator()
         }
     }
 
-    fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) {
+    private fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) {
         val wct = WindowContainerTransaction()
         addMoveToFullscreenChanges(wct, task.token)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             exitDesktopTaskTransitionHandler.startTransition(
-            Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct)
+            Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct, mOnAnimationFinishedCallback)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
+            releaseVisualIndicator()
         }
     }
 
@@ -330,6 +339,16 @@
             ?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) }
     }
 
+    private fun releaseVisualIndicator() {
+        val t = SurfaceControl.Transaction()
+        visualIndicator?.releaseVisualIndicator(t)
+        visualIndicator = null
+        syncQueue.runInSync { transaction ->
+            transaction.merge(t)
+            t.close()
+        }
+    }
+
     override fun getContext(): Context {
         return context
     }
@@ -471,8 +490,7 @@
                         rootTaskDisplayAreaOrganizer)
                 visualIndicator?.createFullscreenIndicatorWithAnimatedBounds()
             } else if (y > statusBarHeight && visualIndicator != null) {
-                visualIndicator?.releaseVisualIndicator()
-                visualIndicator = null
+                releaseVisualIndicator()
             }
         }
     }
@@ -489,8 +507,6 @@
     ) {
         val statusBarHeight = getStatusBarHeight(taskInfo)
         if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
-            visualIndicator?.releaseVisualIndicator()
-            visualIndicator = null
             moveToFullscreenWithAnimation(taskInfo)
         }
     }
@@ -508,6 +524,11 @@
             taskSurface: SurfaceControl,
             y: Float
     ) {
+        // If the motion event is above the status bar, return since we do not need to show the
+        // visual indicator at this point.
+        if (y < getStatusBarHeight(taskInfo)) {
+            return
+        }
         if (visualIndicator == null) {
             visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
                     displayController, context, taskSurface, shellTaskOrganizer,
@@ -535,11 +556,8 @@
             freeformBounds: Rect
     ) {
         moveToDesktopWithAnimation(taskInfo, freeformBounds)
-        visualIndicator?.releaseVisualIndicator()
-        visualIndicator = null
     }
 
-
     private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
         return displayController.getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
     }
@@ -566,7 +584,6 @@
         desktopModeTaskRepository.removeTaskCorners(taskId)
     }
 
-
     /**
      * Adds a listener to find out about changes in the visibility of freeform tasks.
      *
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 9467578..d55fddd 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
@@ -39,6 +39,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -58,6 +59,7 @@
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
     private Point mStartPosition;
+    private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
 
     public EnterDesktopTaskTransitionHandler(
             Transitions transitions) {
@@ -75,9 +77,12 @@
      * Starts Transition of a given type
      * @param type Transition type
      * @param wct WindowContainerTransaction for transition
+     * @param onAnimationEndCallback to be called after animation
      */
     public void startTransition(@WindowManager.TransitionType int type,
-                @NonNull WindowContainerTransaction wct) {
+            @NonNull WindowContainerTransaction wct,
+            Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+        mOnAnimationFinishedCallback = onAnimationEndCallback;
         final IBinder token = mTransitions.startTransition(type, wct, this);
         mPendingTransitionTokens.add(token);
     }
@@ -86,11 +91,14 @@
      * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
      * @param wct WindowContainerTransaction for transition
      * @param startPosition Position of task when transition is triggered
+     * @param onAnimationEndCallback to be called after animation
      */
     public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct,
-            Point startPosition) {
+            Point startPosition,
+            Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
         mStartPosition = startPosition;
-        startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct);
+        startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct,
+                mOnAnimationFinishedCallback);
     }
 
     @Override
@@ -111,7 +119,7 @@
 
             if (change.getMode() == WindowManager.TRANSIT_CHANGE) {
                 transitionHandled |= startChangeTransition(
-                        transition, info.getType(), change, startT, finishCallback);
+                        transition, info.getType(), change, startT, finishT, finishCallback);
             }
         }
 
@@ -125,6 +133,7 @@
             @WindowManager.TransitionType int type,
             @NonNull TransitionInfo.Change change,
             @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         if (!mPendingTransitionTokens.contains(transition)) {
             return false;
@@ -178,6 +187,9 @@
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
+                    if (mOnAnimationFinishedCallback != null) {
+                        mOnAnimationFinishedCallback.accept(finishT);
+                    }
                     mTransitions.getMainExecutor().execute(
                             () -> finishCallback.onTransitionFinished(null, null));
                 }
@@ -204,7 +216,7 @@
             animator.setDuration(FREEFORM_ANIMATION_DURATION);
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
             animator.addUpdateListener(animation -> {
-                final float scale = animation.getAnimatedFraction();
+                final float scale = (float) animation.getAnimatedValue();
                 t.setPosition(sc, mStartPosition.x * (1 - scale), mStartPosition.y * (1 - scale))
                         .setScale(sc, scale, scale)
                         .show(sc)
@@ -213,6 +225,9 @@
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
+                    if (mOnAnimationFinishedCallback != null) {
+                        mOnAnimationFinishedCallback.accept(finishT);
+                    }
                     mTransitions.getMainExecutor().execute(
                             () -> finishCallback.onTransitionFinished(null, null));
                 }
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 fa3eee2..160a83d 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
@@ -42,6 +42,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 
@@ -54,7 +55,7 @@
     private final Context mContext;
     private final Transitions mTransitions;
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
-
+    private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
     private Supplier<SurfaceControl.Transaction> mTransactionSupplier;
 
     public ExitDesktopTaskTransitionHandler(
@@ -76,9 +77,12 @@
      * Starts Transition of a given type
      * @param type Transition type
      * @param wct WindowContainerTransaction for transition
+     * @param onAnimationEndCallback to be called after animation
      */
     public void startTransition(@WindowManager.TransitionType int type,
-            @NonNull WindowContainerTransaction wct) {
+            @NonNull WindowContainerTransaction wct,
+            Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+        mOnAnimationFinishedCallback = onAnimationEndCallback;
         final IBinder token = mTransitions.startTransition(type, wct, this);
         mPendingTransitionTokens.add(token);
     }
@@ -101,7 +105,7 @@
 
             if (change.getMode() == WindowManager.TRANSIT_CHANGE) {
                 transitionHandled |= startChangeTransition(
-                        transition, info.getType(), change, startT, finishCallback);
+                        transition, info.getType(), change, startT, finishT, finishCallback);
             }
         }
 
@@ -116,6 +120,7 @@
             @WindowManager.TransitionType int type,
             @NonNull TransitionInfo.Change change,
             @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         if (!mPendingTransitionTokens.contains(transition)) {
             return false;
@@ -156,6 +161,9 @@
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
+                    if (mOnAnimationFinishedCallback != null) {
+                        mOnAnimationFinishedCallback.accept(finishT);
+                    }
                     mTransitions.getMainExecutor().execute(
                             () -> finishCallback.onTransitionFinished(null, null));
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 091de3a..be2489d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -35,10 +35,14 @@
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
+
 import android.content.ClipDescription;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.HardwareRenderer;
 import android.graphics.PixelFormat;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -50,6 +54,8 @@
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 
+import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.InstanceId;
@@ -58,25 +64,31 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
+import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ExternalMainThread;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 
 /**
  * Handles the global drag and drop handling for the Shell.
  */
-public class DragAndDropController implements DisplayController.OnDisplaysChangedListener,
+public class DragAndDropController implements RemoteCallable<DragAndDropController>,
+        DisplayController.OnDisplaysChangedListener,
         View.OnDragListener, ComponentCallbacks2 {
 
     private static final String TAG = DragAndDropController.class.getSimpleName();
 
     private final Context mContext;
     private final ShellController mShellController;
+    private final ShellCommandHandler mShellCommandHandler;
     private final DisplayController mDisplayController;
     private final DragAndDropEventLogger mLogger;
     private final IconProvider mIconProvider;
@@ -100,6 +112,7 @@
     public static DragAndDropController create(Context context,
             ShellInit shellInit,
             ShellController shellController,
+            ShellCommandHandler shellCommandHandler,
             DisplayController displayController,
             UiEventLogger uiEventLogger,
             IconProvider iconProvider,
@@ -107,19 +120,21 @@
         if (!context.getResources().getBoolean(R.bool.config_enableShellDragDrop)) {
             return null;
         }
-        return new DragAndDropController(context, shellInit, shellController, displayController,
-                uiEventLogger, iconProvider, mainExecutor);
+        return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
+                displayController, uiEventLogger, iconProvider, mainExecutor);
     }
 
     DragAndDropController(Context context,
             ShellInit shellInit,
             ShellController shellController,
+            ShellCommandHandler shellCommandHandler,
             DisplayController displayController,
             UiEventLogger uiEventLogger,
             IconProvider iconProvider,
             ShellExecutor mainExecutor) {
         mContext = context;
         mShellController = shellController;
+        mShellCommandHandler = shellCommandHandler;
         mDisplayController = displayController;
         mLogger = new DragAndDropEventLogger(uiEventLogger);
         mIconProvider = iconProvider;
@@ -137,6 +152,23 @@
         mMainExecutor.executeDelayed(() -> {
             mDisplayController.addDisplayWindowListener(this);
         }, 0);
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP,
+                this::createExternalInterface, this);
+        mShellCommandHandler.addDumpCallback(this::dump, this);
+    }
+
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IDragAndDropImpl(this);
+    }
+
+    @Override
+    public Context getContext() {
+        return mContext;
+    }
+
+    @Override
+    public ShellExecutor getRemoteCallExecutor() {
+        return mMainExecutor;
     }
 
     /**
@@ -156,7 +188,7 @@
         mListeners.remove(listener);
     }
 
-    private void notifyListeners() {
+    private void notifyDragStarted() {
         for (int i = 0; i < mListeners.size(); i++) {
             mListeners.get(i).onDragStarted();
         }
@@ -273,7 +305,7 @@
                 pd.dragLayout.prepare(mDisplayController.getDisplayLayout(displayId),
                         event.getClipData(), loggerSessionId);
                 setDropTargetWindowVisibility(pd, View.VISIBLE);
-                notifyListeners();
+                notifyDragStarted();
                 break;
             case ACTION_DRAG_ENTERED:
                 pd.dragLayout.show();
@@ -327,13 +359,7 @@
     }
 
     private void setDropTargetWindowVisibility(PerDisplay pd, int visibility) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
-                "Set drop target window visibility: displayId=%d visibility=%d",
-                pd.displayId, visibility);
-        pd.rootView.setVisibility(visibility);
-        if (visibility == View.VISIBLE) {
-            pd.rootView.requestApplyInsets();
-        }
+        pd.setWindowVisibility(visibility);
     }
 
     private String getMimeTypes(ClipDescription description) {
@@ -347,6 +373,18 @@
         return mimeTypes;
     }
 
+    /**
+     * Returns if any displays are currently ready to handle a drag/drop.
+     */
+    private boolean isReadyToHandleDrag() {
+        for (int i = 0; i < mDisplayDropTargets.size(); i++) {
+            if (mDisplayDropTargets.valueAt(i).mHasDrawn) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     // Note: Component callbacks are always called on the main thread of the process
     @ExternalMainThread
     @Override
@@ -372,12 +410,53 @@
         // Do nothing
     }
 
-    private static class PerDisplay {
+    /**
+     * Dumps information about this controller.
+     */
+    public void dump(@NonNull PrintWriter pw, String prefix) {
+        pw.println(prefix + TAG);
+        pw.println(prefix + " listeners=" + mListeners.size());
+    }
+    
+    /**
+     * The interface for calls from outside the host process.
+     */
+    @BinderThread
+    private static class IDragAndDropImpl extends IDragAndDrop.Stub
+            implements ExternalInterfaceBinder {
+        private DragAndDropController mController;
+
+        public IDragAndDropImpl(DragAndDropController controller) {
+            mController = controller;
+        }
+
+        /**
+         * Invalidates this instance, preventing future calls from updating the controller.
+         */
+        @Override
+        public void invalidate() {
+            mController = null;
+        }
+
+        @Override
+        public boolean isReadyToHandleDrag() {
+            boolean[] result = new boolean[1];
+            executeRemoteCallWithTaskPermission(mController, "isReadyToHandleDrag",
+                    controller -> result[0] = controller.isReadyToHandleDrag(),
+                    true /* blocking */
+            );
+            return result[0];
+        }
+    }
+
+    private static class PerDisplay implements HardwareRenderer.FrameDrawingCallback {
         final int displayId;
         final Context context;
         final WindowManager wm;
         final FrameLayout rootView;
         final DragLayout dragLayout;
+        // Tracks whether the window has fully drawn since it was last made visible
+        boolean mHasDrawn;
 
         boolean isHandlingDrag;
         // A count of the number of active drags in progress to ensure that we only hide the window
@@ -391,5 +470,25 @@
             rootView = rv;
             dragLayout = dl;
         }
+
+        private void setWindowVisibility(int visibility) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+                    "Set drop target window visibility: displayId=%d visibility=%d",
+                    displayId, visibility);
+            rootView.setVisibility(visibility);
+            if (visibility == View.VISIBLE) {
+                rootView.requestApplyInsets();
+                if (!mHasDrawn && rootView.getViewRootImpl() != null) {
+                    rootView.getViewRootImpl().registerRtFrameCallback(this);
+                }
+            } else {
+                mHasDrawn = false;
+            }
+        }
+
+        @Override
+        public void onFrameDraw(long frame) {
+            mHasDrawn = true;
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index 28f59b5..724a130 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -104,7 +104,7 @@
         setContainerMargin(0, 0, 0, 0); // make sure it's populated
 
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
-        mMarginColor = getResources().getColor(R.color.taskbar_background);
+        mMarginColor = getResources().getColor(R.color.taskbar_background_dark);
         int c = getResources().getColor(android.R.color.system_accent1_500);
         mHighlightColor =  Color.argb(HIGHLIGHT_ALPHA, Color.red(c), Color.green(c), Color.blue(c));
         mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, 0, 0, 0);
@@ -125,7 +125,7 @@
 
     public void onThemeChange() {
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(getContext());
-        mMarginColor = getResources().getColor(R.color.taskbar_background);
+        mMarginColor = getResources().getColor(R.color.taskbar_background_dark);
         mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
 
         if (mMarginPercent > 0) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/IDragAndDrop.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/IDragAndDrop.aidl
new file mode 100644
index 0000000..aeb0c63
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/IDragAndDrop.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop;
+
+/**
+ * Interface that is exposed to remote callers to manipulate drag and drop.
+ */
+interface IDragAndDrop {
+    /**
+     * Returns whether the shell drop target is showing and will handle a drag/drop.
+     */
+    boolean isReadyToHandleDrag() = 1;
+}
+// Last id = 1
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index d3e7f9ca..6cedcf5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -27,6 +27,7 @@
 import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
 import static com.android.wm.shell.pip.PipAnimationController.FRACTION_START;
@@ -524,12 +525,18 @@
             }
         }
 
-        final Rect destinationBounds = getExitDestinationBounds();
+        final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+        final Rect destinationBounds = new Rect(displayBounds);
         final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit)
                 ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
                 : TRANSITION_DIRECTION_LEAVE_PIP;
+        // For exiting to fullscreen, the windowing mode of task will be changed to fullscreen
+        // until the animation is finished. Otherwise if the activity is resumed and focused at the
+        // begin of aniamtion, the app may do something too early to distub the animation.
+        final boolean toFullscreen = destinationBounds.equals(displayBounds);
 
-        if (Transitions.ENABLE_SHELL_TRANSITIONS && direction == TRANSITION_DIRECTION_LEAVE_PIP) {
+        if (Transitions.SHELL_TRANSITIONS_ROTATION || (Transitions.ENABLE_SHELL_TRANSITIONS
+                && !toFullscreen)) {
             // When exit to fullscreen with Shell transition enabled, we update the Task windowing
             // mode directly so that it can also trigger display rotation and visibility update in
             // the same transition if there will be any.
@@ -605,7 +612,7 @@
         removePip();
     }
 
-    private void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) {
+    void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) {
         // Reset the final windowing mode.
         wct.setWindowingMode(mToken, getOutPipWindowingMode());
         // Simply reset the activity mode set prior to the animation running.
@@ -1771,14 +1778,26 @@
      * @return {@code true} if destinationBounds is altered for split screen
      */
     private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut, boolean enterSplit) {
-        if (!enterSplit || !mSplitScreenOptional.isPresent()) {
+        if (mSplitScreenOptional.isEmpty()) {
+            return false;
+        }
+        final SplitScreenController split = mSplitScreenOptional.get();
+        final int position = mTaskInfo.lastParentTaskIdBeforePip > 0
+                ? split.getSplitPosition(mTaskInfo.lastParentTaskIdBeforePip)
+                : SPLIT_POSITION_UNDEFINED;
+        if (position == SPLIT_POSITION_UNDEFINED && !enterSplit) {
             return false;
         }
         final Rect topLeft = new Rect();
         final Rect bottomRight = new Rect();
-        mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
-        destinationBoundsOut.set(isPipToTopLeft()  ? topLeft : bottomRight);
-        return true;
+        split.getStageBounds(topLeft, bottomRight);
+        if (enterSplit) {
+            destinationBoundsOut.set(isPipToTopLeft() ? topLeft : bottomRight);
+            return true;
+        }
+        // Moving to an existing split task.
+        destinationBoundsOut.set(position == SPLIT_POSITION_TOP_OR_LEFT ? topLeft : bottomRight);
+        return false;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 99cb6f78..98db707 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -508,8 +508,16 @@
         currentBounds.offset(-offset.x, -offset.y);
         startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);
 
+        final WindowContainerToken pipTaskToken = pipChange.getContainer();
+        final boolean toFullscreen = pipChange.getEndAbsBounds().equals(
+                mPipBoundsState.getDisplayBounds());
         mFinishCallback = (wct, wctCB) -> {
             mPipOrganizer.onExitPipFinished(taskInfo);
+            if (!Transitions.SHELL_TRANSITIONS_ROTATION && toFullscreen) {
+                wct = wct != null ? wct : new WindowContainerTransaction();
+                wct.setBounds(pipTaskToken, null);
+                mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP);
+            }
             finishCallback.onTransitionFinished(wct, wctCB);
         };
         mFinishTransaction = finishTransaction;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
index bfa6390..5f54f58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -42,4 +42,6 @@
     public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
     // See IDesktopMode.aidl
     public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
+    // See IDragAndDrop.aidl
+    public static final String KEY_EXTRA_SHELL_DRAG_AND_DROP = "extra_shell_drag_and_drop";
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index c7e534a..2faed3a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -202,6 +202,7 @@
     }
 
     void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) {
+        if (mTaskViews.get(taskView) == null) return;
         if (mTaskViews.get(taskView).mVisible == visible) return;
         if (taskView.getTaskInfo() == null) {
             // Nothing to update, task is not yet available
@@ -220,17 +221,19 @@
 
     void updateBoundsState(TaskViewTaskController taskView, Rect boundsOnScreen) {
         TaskViewRequestedState state = mTaskViews.get(taskView);
+        if (state == null) return;
         state.mBounds.set(boundsOnScreen);
     }
 
     void updateVisibilityState(TaskViewTaskController taskView, boolean visible) {
         TaskViewRequestedState state = mTaskViews.get(taskView);
+        if (state == null) return;
         state.mVisible = visible;
     }
 
     void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) {
         TaskViewRequestedState state = mTaskViews.get(taskView);
-        if (Objects.equals(boundsOnScreen, state.mBounds)) {
+        if (state == null || Objects.equals(boundsOnScreen, state.mBounds)) {
             return;
         }
         state.mBounds.set(boundsOnScreen);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
index ba364f8..0cede90 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
@@ -18,25 +18,29 @@
 
 import static android.os.Build.IS_USER;
 
-import static com.android.wm.shell.WmShellTransitionTraceProto.MAGIC_NUMBER;
-import static com.android.wm.shell.WmShellTransitionTraceProto.MAGIC_NUMBER_H;
-import static com.android.wm.shell.WmShellTransitionTraceProto.MAGIC_NUMBER_L;
+import static com.android.wm.shell.nano.WmShellTransitionTraceProto.MAGIC_NUMBER_H;
+import static com.android.wm.shell.nano.WmShellTransitionTraceProto.MAGIC_NUMBER_L;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.util.Log;
-import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.TraceBuffer;
+import com.android.wm.shell.nano.HandlerMapping;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 
+import com.google.protobuf.nano.MessageNano;
+
 import java.io.File;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Queue;
 
 /**
  * Helper class to collect and dump transition traces.
@@ -54,8 +58,35 @@
     private final Object mEnabledLock = new Object();
     private boolean mActiveTracingEnabled = false;
 
-    private final TraceBuffer mTraceBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY,
-            (proto) -> handleOnEntryRemovedFromTrace(proto));
+    private final TraceBuffer.ProtoProvider mProtoProvider =
+            new TraceBuffer.ProtoProvider<MessageNano,
+                com.android.wm.shell.nano.WmShellTransitionTraceProto,
+                com.android.wm.shell.nano.Transition>() {
+        @Override
+        public int getItemSize(MessageNano proto) {
+            return proto.getCachedSize();
+        }
+
+        @Override
+        public byte[] getBytes(MessageNano proto) {
+            return MessageNano.toByteArray(proto);
+        }
+
+        @Override
+        public void write(
+                com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto,
+                Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os)
+                        throws IOException {
+            encapsulatingProto.transitions = buffer.toArray(
+                    new com.android.wm.shell.nano.Transition[0]);
+            os.write(getBytes(encapsulatingProto));
+        }
+    };
+    private final TraceBuffer<MessageNano,
+            com.android.wm.shell.nano.WmShellTransitionTraceProto,
+            com.android.wm.shell.nano.Transition> mTraceBuffer
+                    = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider,
+                            (proto) -> handleOnEntryRemovedFromTrace(proto));
     private final Map<Object, Runnable> mRemovedFromTraceCallbacks = new HashMap<>();
 
     private final Map<Transitions.TransitionHandler, Integer> mHandlerIds = new HashMap<>();
@@ -78,26 +109,20 @@
             mHandlerIds.put(handler, handlerId);
         }
 
-        ProtoOutputStream outputStream = new ProtoOutputStream();
-        final long protoToken =
-                outputStream.start(com.android.wm.shell.WmShellTransitionTraceProto.TRANSITIONS);
-
-        outputStream.write(com.android.wm.shell.Transition.ID, transitionId);
-        outputStream.write(com.android.wm.shell.Transition.DISPATCH_TIME_NS,
-                SystemClock.elapsedRealtimeNanos());
-        outputStream.write(com.android.wm.shell.Transition.HANDLER, handlerId);
-
-        outputStream.end(protoToken);
+        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+        proto.id = transitionId;
+        proto.dispatchTimeNs = SystemClock.elapsedRealtimeNanos();
+        proto.handler = handlerId;
 
         final int useCountAfterAdd = mHandlerUseCountInTrace.getOrDefault(handler, 0) + 1;
         mHandlerUseCountInTrace.put(handler, useCountAfterAdd);
 
-        mRemovedFromTraceCallbacks.put(outputStream, () -> {
+        mRemovedFromTraceCallbacks.put(proto, () -> {
             final int useCountAfterRemove = mHandlerUseCountInTrace.get(handler) - 1;
             mHandlerUseCountInTrace.put(handler, useCountAfterRemove);
         });
 
-        mTraceBuffer.add(outputStream);
+        mTraceBuffer.add(proto);
     }
 
     /**
@@ -107,18 +132,12 @@
      * @param playingTransitionId The id of the transition we was to merge the transition into.
      */
     public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
-        ProtoOutputStream outputStream = new ProtoOutputStream();
-        final long protoToken =
-                outputStream.start(com.android.wm.shell.WmShellTransitionTraceProto.TRANSITIONS);
+        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+        proto.id = mergeRequestedTransitionId;
+        proto.mergeRequestTimeNs = SystemClock.elapsedRealtimeNanos();
+        proto.mergedInto = playingTransitionId;
 
-        outputStream.write(com.android.wm.shell.Transition.ID, mergeRequestedTransitionId);
-        outputStream.write(com.android.wm.shell.Transition.MERGE_REQUEST_TIME_NS,
-                SystemClock.elapsedRealtimeNanos());
-        outputStream.write(com.android.wm.shell.Transition.MERGED_INTO, playingTransitionId);
-
-        outputStream.end(protoToken);
-
-        mTraceBuffer.add(outputStream);
+        mTraceBuffer.add(proto);
     }
 
     /**
@@ -128,18 +147,12 @@
      * @param playingTransitionId The id of the transition the transition was merged into.
      */
     public void logMerged(int mergedTransitionId, int playingTransitionId) {
-        ProtoOutputStream outputStream = new ProtoOutputStream();
-        final long protoToken =
-                outputStream.start(com.android.wm.shell.WmShellTransitionTraceProto.TRANSITIONS);
+        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+        proto.id = mergedTransitionId;
+        proto.mergeTimeNs = SystemClock.elapsedRealtimeNanos();
+        proto.mergedInto = playingTransitionId;
 
-        outputStream.write(com.android.wm.shell.Transition.ID, mergedTransitionId);
-        outputStream.write(
-                com.android.wm.shell.Transition.MERGE_TIME_NS, SystemClock.elapsedRealtimeNanos());
-        outputStream.write(com.android.wm.shell.Transition.MERGED_INTO, playingTransitionId);
-
-        outputStream.end(protoToken);
-
-        mTraceBuffer.add(outputStream);
+        mTraceBuffer.add(proto);
     }
 
     /**
@@ -148,17 +161,11 @@
      * @param transitionId The id of the transition that was aborted.
      */
     public void logAborted(int transitionId) {
-        ProtoOutputStream outputStream = new ProtoOutputStream();
-        final long protoToken =
-                outputStream.start(com.android.wm.shell.WmShellTransitionTraceProto.TRANSITIONS);
+        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+        proto.id = transitionId;
+        proto.abortTimeNs = SystemClock.elapsedRealtimeNanos();
 
-        outputStream.write(com.android.wm.shell.Transition.ID, transitionId);
-        outputStream.write(
-                com.android.wm.shell.Transition.ABORT_TIME_NS, SystemClock.elapsedRealtimeNanos());
-
-        outputStream.end(protoToken);
-
-        mTraceBuffer.add(outputStream);
+        mTraceBuffer.add(proto);
     }
 
     /**
@@ -230,8 +237,9 @@
     private void writeTraceToFileLocked(@Nullable PrintWriter pw, File file) {
         Trace.beginSection("TransitionTracer#writeTraceToFileLocked");
         try {
-            ProtoOutputStream proto = new ProtoOutputStream();
-            proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+            com.android.wm.shell.nano.WmShellTransitionTraceProto proto =
+                    new com.android.wm.shell.nano.WmShellTransitionTraceProto();
+            proto.magicNumber = MAGIC_NUMBER_VALUE;
             writeHandlerMappingToProto(proto);
             int pid = android.os.Process.myPid();
             LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath()
@@ -243,19 +251,21 @@
         Trace.endSection();
     }
 
-    private void writeHandlerMappingToProto(ProtoOutputStream outputStream) {
+    private void writeHandlerMappingToProto(
+            com.android.wm.shell.nano.WmShellTransitionTraceProto proto) {
+        ArrayList<com.android.wm.shell.nano.HandlerMapping> handlerMappings = new ArrayList<>();
         for (Transitions.TransitionHandler handler : mHandlerUseCountInTrace.keySet()) {
             final int count = mHandlerUseCountInTrace.get(handler);
             if (count > 0) {
-                final long protoToken = outputStream.start(
-                        com.android.wm.shell.WmShellTransitionTraceProto.HANDLER_MAPPINGS);
-                outputStream.write(com.android.wm.shell.HandlerMapping.ID,
-                        mHandlerIds.get(handler));
-                outputStream.write(com.android.wm.shell.HandlerMapping.NAME,
-                        handler.getClass().getName());
-                outputStream.end(protoToken);
+                com.android.wm.shell.nano.HandlerMapping mapping =
+                        new com.android.wm.shell.nano.HandlerMapping();
+                mapping.id = mHandlerIds.get(handler);
+                mapping.name = handler.getClass().getName();
+                handlerMappings.add(mapping);
             }
         }
+        proto.handlerMappings = handlerMappings.toArray(
+                new com.android.wm.shell.nano.HandlerMapping[0]);
     }
 
     private void handleOnEntryRemovedFromTrace(Object proto) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 12edc35..dffbedd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -111,7 +111,6 @@
     private ValueAnimator mDragToDesktopValueAnimator;
     private final Rect mDragToDesktopAnimationStartBounds = new Rect();
     private boolean mDragToDesktopAnimationStarted;
-    private float mCaptionDragStartX;
 
     public DesktopModeWindowDecorViewModel(
             Context context,
@@ -525,7 +524,6 @@
             DesktopModeWindowDecoration relevantDecor) {
         switch (ev.getActionMasked()) {
             case MotionEvent.ACTION_DOWN: {
-                mCaptionDragStartX = ev.getX();
                 // Begin drag through status bar if applicable.
                 if (relevantDecor != null) {
                     mDragToDesktopAnimationStartBounds.set(
@@ -580,8 +578,9 @@
                 }
                 if (mTransitionDragActive) {
                     mDesktopTasksController.ifPresent(
-                            c -> c.onDragPositioningMoveThroughStatusBar(relevantDecor.mTaskInfo,
-                            relevantDecor.mTaskSurface, ev.getY()));
+                            c -> c.onDragPositioningMoveThroughStatusBar(
+                                    relevantDecor.mTaskInfo,
+                                    relevantDecor.mTaskSurface, ev.getY()));
                     final int statusBarHeight = getStatusBarHeight(
                             relevantDecor.mTaskInfo.displayId);
                     if (ev.getY() > statusBarHeight) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
index 11c5951..6178156 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -18,15 +18,18 @@
 
 import android.content.Context
 import android.system.helpers.CommandsHelper
+import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import android.tools.device.flicker.legacy.IFlickerTestData
-import com.android.server.wm.flicker.helpers.LetterboxAppHelper
-import com.android.server.wm.flicker.helpers.setRotation
 import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.layerKeepVisible
+import org.junit.After
 import org.junit.Assume
 import org.junit.Before
 import org.junit.runners.Parameterized
@@ -35,7 +38,7 @@
     protected val context: Context = instrumentation.context
     protected val letterboxApp = LetterboxAppHelper(instrumentation)
     lateinit var cmdHelper: CommandsHelper
-    lateinit var letterboxStyle: HashMap<String, String>
+    private lateinit var letterboxStyle: HashMap<String, String>
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -45,12 +48,22 @@
                 letterboxApp.launchViaIntent(wmHelper)
                 setEndRotation()
             }
+            teardown {
+                letterboxApp.exit(wmHelper)
+            }
         }
 
     @Before
     fun before() {
         cmdHelper = CommandsHelper.getInstance(instrumentation)
         Assume.assumeTrue(tapl.isTablet && isIgnoreOrientationRequest())
+        letterboxStyle = mapLetterboxStyle()
+        setLetterboxEducationEnabled(false)
+    }
+
+    @After
+    fun after() {
+        resetLetterboxEducationEnabled()
     }
 
     private fun mapLetterboxStyle(): HashMap<String, String> {
@@ -67,6 +80,22 @@
         return map
     }
 
+    private fun getLetterboxStyle(): HashMap<String, String> {
+        if (!::letterboxStyle.isInitialized) {
+            letterboxStyle = mapLetterboxStyle()
+        }
+        return letterboxStyle
+    }
+
+    private fun resetLetterboxEducationEnabled() {
+        val enabled = getLetterboxStyle().getValue("Is education enabled")
+        cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled")
+    }
+
+    private fun setLetterboxEducationEnabled(enabled: Boolean) {
+        cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled")
+    }
+
     private fun isIgnoreOrientationRequest(): Boolean {
         val res = cmdHelper.executeShellCommand("wm get-ignore-orientation-request")
         return res != null && res.contains("true")
@@ -89,10 +118,7 @@
 
     /** Only run on tests with config_letterboxActivityCornersRadius != 0 in devices */
     private fun assumeLetterboxRoundedCornersEnabled() {
-        if (!::letterboxStyle.isInitialized) {
-            letterboxStyle = mapLetterboxStyle()
-        }
-        Assume.assumeTrue(letterboxStyle.getValue("Corner radius") != "0")
+        Assume.assumeTrue(getLetterboxStyle().getValue("Corner radius") != "0")
     }
 
     fun assertLetterboxAppVisibleAtStartAndEnd() {
@@ -100,12 +126,20 @@
         flicker.appWindowIsVisibleAtEnd(letterboxApp)
     }
 
+    fun assertAppLetterboxedAtEnd() =
+            flicker.assertLayersEnd { isVisible(ComponentNameMatcher.LETTERBOX) }
+
+    fun assertAppLetterboxedAtStart() =
+            flicker.assertLayersStart { isVisible(ComponentNameMatcher.LETTERBOX) }
+
+    fun assertLetterboxAppLayerKeepVisible() = flicker.layerKeepVisible(letterboxApp)
+
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation
-         * modes.
+         * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index f212a4e..c2141a3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -70,6 +70,10 @@
     @Test
     fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtEndHasRoundedCorners()
 
+    @Postsubmit
+    @Test
+    fun appIsLetterboxedAtEnd() = assertAppLetterboxedAtEnd()
+
     /**
      * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
      * flicker, and disappears before the transition is complete
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
index 8e75439..b0e1a42 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
@@ -53,25 +53,32 @@
         get() = {
             super.transition(this)
             transitions { letterboxApp.clickRestart(wmHelper) }
-            teardown { letterboxApp.exit(wmHelper) }
         }
 
     @Postsubmit @Test fun appVisibleAtStartAndEnd() = assertLetterboxAppVisibleAtStartAndEnd()
 
     @Postsubmit
     @Test
-    fun appLayerVisibilityChanges() {
-        flicker.assertLayers {
-            this.isVisible(letterboxApp)
+    fun appWindowVisibilityChanges() {
+        flicker.assertWm {
+            this.isAppWindowVisible(letterboxApp)
                 .then()
-                .isInvisible(letterboxApp)
+                .isAppWindowInvisible(letterboxApp) // animatingExit true
                 .then()
-                .isVisible(letterboxApp)
+                .isAppWindowVisible(letterboxApp) // Activity finish relaunching
         }
     }
 
     @Postsubmit
     @Test
+    fun appLayerKeepVisible() = assertLetterboxAppLayerKeepVisible()
+
+    @Postsubmit
+    @Test
+    fun appIsLetterboxedAtStart() = assertAppLetterboxedAtStart()
+
+    @Postsubmit
+    @Test
     fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtStartHasRoundedCorners()
 
     /** Checks that the visible region of [letterboxApp] is still within display bounds */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
index 6199e0b..8592dea 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
@@ -94,7 +94,7 @@
         WindowContainerTransaction wct = new WindowContainerTransaction();
         doReturn(mToken).when(mTransitions)
                 .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
-        mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct);
+        mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
 
         TransitionInfo.Change change =
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
@@ -115,7 +115,7 @@
         WindowContainerTransaction wct = new WindowContainerTransaction();
         doReturn(mToken).when(mTransitions)
                 .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
-        mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct);
+        mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
 
         TransitionInfo.Change change =
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
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 4fad054..265b10d 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
@@ -100,7 +100,7 @@
         doReturn(mToken).when(mTransitions)
                 .startTransition(transitionType, wct, mExitDesktopTaskTransitionHandler);
 
-        mExitDesktopTaskTransitionHandler.startTransition(transitionType, wct);
+        mExitDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
 
         TransitionInfo.Change change =
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FULLSCREEN);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index 523cb66..54f36f6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -48,6 +48,7 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
@@ -71,6 +72,8 @@
     @Mock
     private ShellController mShellController;
     @Mock
+    private ShellCommandHandler mShellCommandHandler;
+    @Mock
     private DisplayController mDisplayController;
     @Mock
     private UiEventLogger mUiEventLogger;
@@ -89,7 +92,8 @@
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
         mController = new DragAndDropController(mContext, mShellInit, mShellController,
-                mDisplayController, mUiEventLogger, mIconProvider, mMainExecutor);
+                mShellCommandHandler, mDisplayController, mUiEventLogger, mIconProvider,
+                mMainExecutor);
         mController.onInit();
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 4559095..9d56686 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -179,4 +179,23 @@
                 mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE);
         assertThat(pendingBounds2).isNull();
     }
+
+    @Test
+    public void testSetTaskVisibility_taskRemoved_noNPE() {
+        mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
+
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+        mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, false);
+    }
+
+    @Test
+    public void testSetTaskBounds_taskRemoved_noNPE() {
+        mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
+
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+        mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
+                new Rect(0, 0, 100, 100));
+    }
 }
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
index 0d39f0e..974a5d0 100644
--- a/libs/hwui/Tonemapper.cpp
+++ b/libs/hwui/Tonemapper.cpp
@@ -97,7 +97,6 @@
                 .inputDataspace = sourceDataspace,
                 .outputDataspace = destinationDataspace,
                 .undoPremultipliedAlpha = source.alphaType() == kPremul_SkAlphaType,
-                .fakeInputDataspace = destinationDataspace,
                 .type = shaders::LinearEffect::SkSLType::ColorFilter};
         constexpr float kMaxDisplayBrightnessNits = 1000.f;
         constexpr float kCurrentDisplayBrightnessNits = 500.f;
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 7e9d44f..c00a270 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -29,6 +29,7 @@
 #include "Layer.h"
 #include "Properties.h"
 #include "RenderThread.h"
+#include "VulkanManager.h"
 #include "pipeline/skia/ATraceMemoryDump.h"
 #include "pipeline/skia/ShaderCache.h"
 #include "pipeline/skia/SkiaMemoryTracer.h"
@@ -182,8 +183,14 @@
     }
     log.appendFormat("Contexts: %zu (stopped = %zu)\n", mCanvasContexts.size(), stoppedContexts);
 
+    auto vkInstance = VulkanManager::peekInstance();
     if (!mGrContext) {
-        log.appendFormat("No GPU context.\n");
+        if (!vkInstance) {
+            log.appendFormat("No GPU context.\n");
+        } else {
+            log.appendFormat("No GrContext; however %d remaining Vulkan refs",
+                             vkInstance->getStrongCount() - 1);
+        }
         return;
     }
     std::vector<skiapipeline::ResourcePair> cpuResourceMap = {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index f198bca..4cffc6c 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -107,11 +107,11 @@
 #define GET_INST_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(mInstance, "vk" #F)
 #define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(mDevice, "vk" #F)
 
-sp<VulkanManager> VulkanManager::getInstance() {
-    // cache a weakptr to the context to enable a second thread to share the same vulkan state
-    static wp<VulkanManager> sWeakInstance = nullptr;
-    static std::mutex sLock;
+// cache a weakptr to the context to enable a second thread to share the same vulkan state
+static wp<VulkanManager> sWeakInstance = nullptr;
+static std::mutex sLock;
 
+sp<VulkanManager> VulkanManager::getInstance() {
     std::lock_guard _lock{sLock};
     sp<VulkanManager> vulkanManager = sWeakInstance.promote();
     if (!vulkanManager.get()) {
@@ -122,6 +122,11 @@
     return vulkanManager;
 }
 
+sp<VulkanManager> VulkanManager::peekInstance() {
+    std::lock_guard _lock{sLock};
+    return sWeakInstance.promote();
+}
+
 VulkanManager::~VulkanManager() {
     if (mDevice != VK_NULL_HANDLE) {
         mDeviceWaitIdle(mDevice);
@@ -404,9 +409,13 @@
     }
 }
 
-sk_sp<GrDirectContext> VulkanManager::createContext(const GrContextOptions& options,
-                                                    ContextType contextType) {
+static void onGrContextReleased(void* context) {
+    VulkanManager* manager = (VulkanManager*)context;
+    manager->decStrong((void*)onGrContextReleased);
+}
 
+sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options,
+                                                    ContextType contextType) {
     GrVkBackendContext backendContext;
     backendContext.fInstance = mInstance;
     backendContext.fPhysicalDevice = mPhysicalDevice;
@@ -418,6 +427,11 @@
     backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2;
     backendContext.fGetProc = sSkiaGetProp;
 
+    LOG_ALWAYS_FATAL_IF(options.fContextDeleteProc != nullptr, "Conflicting fContextDeleteProcs!");
+    this->incStrong((void*)onGrContextReleased);
+    options.fContextDeleteContext = this;
+    options.fContextDeleteProc = onGrContextReleased;
+
     return GrDirectContext::MakeVulkan(backendContext, options);
 }
 
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index c5196ee..00a40c0 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -66,6 +66,7 @@
 class VulkanManager final : public RefBase {
 public:
     static sp<VulkanManager> getInstance();
+    static sp<VulkanManager> peekInstance();
 
     // Sets up the vulkan context that is shared amonst all clients of the VulkanManager. This must
     // be call once before use of the VulkanManager. Multiple calls after the first will simiply
@@ -109,7 +110,7 @@
     };
 
     // returns a Skia graphic context used to draw content on the specified thread
-    sk_sp<GrDirectContext> createContext(const GrContextOptions& options,
+    sk_sp<GrDirectContext> createContext(GrContextOptions& options,
                                          ContextType contextType = ContextType::kRenderThread);
 
     uint32_t getDriverVersion() const { return mDriverVersion; }
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index bffe137..913af8a 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -284,7 +284,9 @@
         case HAL_DATASPACE_TRANSFER_GAMMA2_8:
             return SkColorSpace::MakeRGB({2.8f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut);
         case HAL_DATASPACE_TRANSFER_ST2084:
-            return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut);
+            return SkColorSpace::MakeRGB({-2.0, -1.555223, 1.860454, 32 / 2523.0, 2413 / 128.0,
+                                          -2392 / 128.0, 8192 / 1305.0},
+                                         gamut);
         case HAL_DATASPACE_TRANSFER_SMPTE_170M:
             return SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut);
         case HAL_DATASPACE_TRANSFER_UNSPECIFIED:
@@ -427,10 +429,10 @@
 }
 
 // Skia skcms' default HLG maps encoded [0, 1] to linear [1, 12] in order to follow ARIB
-// but LinearEffect expects a decoded [0, 1] range instead to follow Rec 2100.
+// but LinearEffect expects to map 1.0 == 203 nits
 std::optional<skcms_TransferFunction> GetHLGScaleTransferFunction() {
     skcms_TransferFunction hlgFn;
-    if (skcms_TransferFunction_makeScaledHLGish(&hlgFn, 1.f / 12.f, 2.f, 2.f, 1.f / 0.17883277f,
+    if (skcms_TransferFunction_makeScaledHLGish(&hlgFn, 0.314509843, 2.f, 2.f, 1.f / 0.17883277f,
                                                 0.28466892f, 0.55991073f)) {
         return std::make_optional<skcms_TransferFunction>(hlgFn);
     }
diff --git a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
index 6092ac5..82fc33e 100644
--- a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
@@ -15,8 +15,8 @@
  */
 package android.media.soundtrigger_middleware;
 
-import android.media.soundtrigger.RecognitionEvent;
-import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
 
 /**
  * Main interface for a client to get notifications of events coming from this module.
@@ -31,7 +31,7 @@
      * In case of abortion, the caller may retry after the next onRecognitionAvailabilityChange()
      * callback.
      */
-    void onRecognition(int modelHandle, in RecognitionEvent event, int captureSession);
+    void onRecognition(int modelHandle, in RecognitionEventSys event, int captureSession);
      /**
       * Invoked whenever a phrase recognition event is triggered (typically, on recognition, but
       * also in case of external aborting of a recognition or a forced recognition event - see the
@@ -39,7 +39,7 @@
       * In case of abortion, the caller may retry after the next onRecognitionAvailabilityChange()
       * callback.
       */
-    void onPhraseRecognition(int modelHandle, in PhraseRecognitionEvent event, int captureSession);
+    void onPhraseRecognition(int modelHandle, in PhraseRecognitionEventSys event, int captureSession);
     /**
      * Notifies the client that some start/load operations that have previously failed for resource
      * reasons (threw a ServiceSpecificException(RESOURCE_CONTENTION) or have been preempted) may
diff --git a/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl
new file mode 100644
index 0000000..6c912ed
--- /dev/null
+++ b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger.PhraseRecognitionEvent;
+
+/**
+ * Wrapper to android.media.soundtrigger.RecognitionEvent providing additional fields used by the
+ * framework.
+ */
+parcelable PhraseRecognitionEventSys {
+
+    PhraseRecognitionEvent phraseRecognitionEvent;
+    /**
+     * Timestamp of when the trigger event from SoundTriggerHal was received by the
+     * framework.
+     *
+     * <p>same units and timebase as {@link SystemClock#elapsedRealtime()}.
+     * The value will be -1 if the event was not generated from the HAL.
+     */
+    // @ElapsedRealtimeLong
+    long halEventReceivedMillis = -1;
+}
diff --git a/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl b/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl
new file mode 100644
index 0000000..84e327d
--- /dev/null
+++ b/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger.RecognitionEvent;
+
+/**
+ * Wrapper to android.media.soundtrigger.RecognitionEvent providing additional fields used by the
+ * framework.
+ */
+parcelable RecognitionEventSys {
+
+    RecognitionEvent recognitionEvent;
+    /**
+     * Timestamp of when the trigger event from SoundTriggerHal was received by the
+     * framework.
+     *
+     * <p>same units and timebase as {@link SystemClock#elapsedRealtime()}.
+     * The value will be -1 if the event was not generated from the HAL.
+     */
+    // @ElapsedRealtimeLong
+    long halEventReceivedMillis = -1;
+}
diff --git a/native/android/activity_manager.cpp b/native/android/activity_manager.cpp
index 155a355..bc6a84f 100644
--- a/native/android/activity_manager.cpp
+++ b/native/android/activity_manager.cpp
@@ -45,7 +45,7 @@
     void onUidIdle(uid_t uid, bool disabled) override;
     void onUidStateChanged(uid_t uid, int32_t procState, int64_t procStateSeq,
                            int32_t capability) override;
-    void onUidProcAdjChanged(uid_t uid) override;
+    void onUidProcAdjChanged(uid_t uid, int32_t adj) override;
 
     // IBinder::DeathRecipient implementation
     void binderDied(const wp<IBinder>& who) override;
@@ -121,7 +121,7 @@
 
 void UidObserver::onUidIdle(uid_t uid __unused, bool disabled __unused) {}
 
-void UidObserver::onUidProcAdjChanged(uid_t uid __unused) {}
+void UidObserver::onUidProcAdjChanged(uid_t uid __unused, int32_t adj __unused) {}
 
 void UidObserver::onUidStateChanged(uid_t uid, int32_t procState,
                                     int64_t procStateSeq __unused,
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index 5f067e9..b8887390 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -60,6 +60,7 @@
     @NonNull private Intent mIntent;
     @NonNull private URL mUrl;
     @TelephonyManager.PremiumCapability protected int mCapability;
+    private boolean mIsUserTriggeredFinish;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -71,6 +72,7 @@
                 SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
         String url = mIntent.getStringExtra(SlicePurchaseController.EXTRA_PURCHASE_URL);
         mApplicationContext = getApplicationContext();
+        mIsUserTriggeredFinish = true;
         logd("onCreate: subId=" + subId + ", capability="
                 + TelephonyManager.convertPremiumCapabilityToString(mCapability) + ", url=" + url);
 
@@ -153,12 +155,20 @@
 
     @Override
     protected void onDestroy() {
-        logd("onDestroy: User canceled the purchase by closing the application.");
-        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
-                mIntent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
+        if (mIsUserTriggeredFinish) {
+            logd("onDestroy: User canceled the purchase by closing the application.");
+            SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+                    mIntent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
+        }
         super.onDestroy();
     }
 
+    @Override
+    public void finishAndRemoveTask() {
+        mIsUserTriggeredFinish = false;
+        super.finishAndRemoveTask();
+    }
+
     private void setupWebView() {
         // Create WebView
         mWebView = new WebView(this);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
index f594bf2..7ed1816 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
@@ -123,12 +123,11 @@
             viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more);
         }
 
-        setAccessibility(view, viewType,
-                AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expand, 0);
-
         // Add expand buttons if the permissions are more than PERMISSION_SIZE in this list also
         // make the summary invisible by default.
         if (mPermissions.size() > PERMISSION_SIZE) {
+            setAccessibility(view, viewType,
+                    AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expand, 0);
 
             viewHolder.mPermissionSummary.setVisibility(View.GONE);
 
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 7d71bd5..6df0778 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -24,8 +24,8 @@
   <string name="string_cancel">Cancel</string>
   <!-- This is a label for a button that takes user to the next screen. [CHAR LIMIT=20] -->
   <string name="string_continue">Continue</string>
-  <!-- This is a label for a button that links to different places where the user can save their passkeys. [CHAR LIMIT=20] -->
-  <string name="string_more_options">More options</string>
+  <!-- This is a label for a button that leads to a holistic view of all different options where the user can save their new app credential. [CHAR LIMIT=20] -->
+  <string name="string_more_options">Save another way</string>
   <!-- This is a label for a button that links to additional information about passkeys. [CHAR LIMIT=20] -->
   <string name="string_learn_more">Learn more</string>
   <!-- This is a label for content description for show password icon button. -->
@@ -120,6 +120,8 @@
   <!-- Strings for the get flow. -->
   <!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
   <string name="get_dialog_title_use_passkey_for">Use your saved passkey for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+  <!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved password to sign in to the app. [CHAR LIMIT=200] -->
+  <string name="get_dialog_title_use_password_for">Use your saved password for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
   <!-- This appears as the title of the dialog asking for user confirmation to use the single user credential (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
   <string name="get_dialog_title_use_sign_in_for">Use your sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
   <!-- This appears as the title of the dialog asking for user to make a choice from various available user credentials (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 1fb5e3f..54a8678d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -25,6 +25,7 @@
 import android.os.ResultReceiver
 import android.util.Log
 import androidx.activity.ComponentActivity
+import androidx.activity.OnBackPressedCallback
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.compose.setContent
 import androidx.activity.viewModels
@@ -48,11 +49,12 @@
 class CredentialSelectorActivity : ComponentActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+        Log.d(Constants.LOG_TAG, "Creating new CredentialSelectorActivity")
         overrideActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN,
             0, 0)
         overrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE,
             0, 0)
-        Log.d(Constants.LOG_TAG, "Creating new CredentialSelectorActivity")
+
         try {
             val (isCancellationRequest, shouldShowCancellationUi, _) =
                 maybeCancelUIUponRequest(intent)
@@ -61,6 +63,18 @@
             }
             val userConfigRepo = UserConfigRepo(this)
             val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
+
+            val backPressedCallback = object : OnBackPressedCallback(
+                true // default to enabled
+            ) {
+                override fun handleOnBackPressed() {
+                    credManRepo.onUserCancel()
+                    Log.d(Constants.LOG_TAG, "Activity back triggered: finish the activity.")
+                    [email protected]()
+                }
+            }
+            onBackPressedDispatcher.addCallback(this, backPressedCallback)
+
             setContent {
                 PlatformTheme {
                     CredentialManagerBottomSheet(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 00c2f1a..a35310c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -482,7 +482,8 @@
                     CredentialType.PASSWORD,
                     appLabel,
                     context.getDrawable(R.drawable.ic_password_24) ?: return null,
-                    preferImmediatelyAvailableCredentials = false,
+                    preferImmediatelyAvailableCredentials =
+                    createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
                     appPreferredDefaultProviderId = appPreferredDefaultProviderId,
                     userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
                 )
@@ -509,7 +510,8 @@
                         appName = appLabel,
                         typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context)
                             ?: context.getDrawable(R.drawable.ic_other_sign_in_24) ?: return null,
-                        preferImmediatelyAvailableCredentials = false,
+                        preferImmediatelyAvailableCredentials =
+                        createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
                         appPreferredDefaultProviderId = appPreferredDefaultProviderId,
                         userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
                     )
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 706666c..2e49dd5 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -841,7 +841,8 @@
                  Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
                  Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
                  Settings.Secure.UI_TRANSLATION_ENABLED,
-                 Settings.Secure.CREDENTIAL_SERVICE);
+                 Settings.Secure.CREDENTIAL_SERVICE,
+                 Settings.Secure.CREDENTIAL_SERVICE_PRIMARY);
 
     @Test
     public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index a110f56..8b3fd41 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -17,12 +17,12 @@
  */
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.shell"
-        coreApp="true"
-        android:sharedUserId="android.uid.shell"
-        >
+   package="com.android.shell"
+   coreApp="true"
+   android:sharedUserId="android.uid.shell"
+   >
 
-        <!-- Standard permissions granted to the shell. -->
+    <!-- Standard permissions granted to the shell. -->
     <uses-permission android:name="android.permission.MANAGE_HEALTH_PERMISSIONS" />
     <uses-permission android:name="android.permission.MANAGE_HEALTH_DATA" />
     <uses-permission android:name="android.permission.health.READ_EXERCISE_ROUTE" />
@@ -125,7 +125,7 @@
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
-    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
     <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
     <!-- BLUETOOTH_PRIVILEGED is needed for testing purposes only. -->
     <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
@@ -136,7 +136,7 @@
     <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.USE_RESERVED_DISK" />
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
     <!-- System tool permissions granted to the shell. -->
     <uses-permission android:name="android.permission.REAL_GET_TASKS" />
     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
@@ -231,16 +231,16 @@
     <uses-permission android:name="android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS" />
     <uses-permission android:name="android.permission.CLEAR_FREEZE_PERIOD" />
     <uses-permission android:name="android.permission.MODIFY_QUIET_MODE" />
-    <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE"/>
-    <uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE"/>
-    <uses-permission android:name="android.permission.READ_LOWPAN_CREDENTIAL"/>
+    <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE" />
+    <uses-permission android:name="android.permission.READ_LOWPAN_CREDENTIAL" />
     <uses-permission android:name="android.permission.BLUETOOTH_STACK" />
     <uses-permission android:name="android.permission.GET_ACCOUNTS" />
     <uses-permission android:name="android.permission.RETRIEVE_WINDOW_TOKEN" />
     <uses-permission android:name="android.permission.FRAME_STATS" />
     <uses-permission android:name="android.permission.BIND_APPWIDGET" />
     <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
-    <uses-permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
+    <uses-permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS" />
     <uses-permission android:name="android.permission.CHANGE_APP_IDLE_STATE" />
     <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
     <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
@@ -304,7 +304,7 @@
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.BACKGROUND_CAMERA" />
     <uses-permission android:name="android.permission.SYSTEM_CAMERA" />
-      <!-- Permissions needed to test onCameraOpened/Closed callbacks -->
+    <!-- Permissions needed to test onCameraOpened/Closed callbacks -->
     <uses-permission android:name="android.permission.CAMERA_OPEN_CLOSE_LISTENER" />
     <!-- Permissions needed for CTS camera test: RecordingTest.java when assuming shell id -->
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
@@ -344,7 +344,7 @@
     <uses-permission android:name="android.permission.LOADER_USAGE_STATS" />
 
     <!-- Permission required for storage tests - FuseDaemonHostTest -->
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
 
     <!-- Permission needed to run network tests in CTS -->
     <uses-permission android:name="android.permission.MANAGE_TEST_NETWORKS" />
@@ -386,54 +386,54 @@
     <uses-permission android:name="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE" />
 
     <!-- Permission required for CTS test - CrossProfileAppsHostSideTest -->
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES" />
 
     <!-- Permission required for CTS test - CrossProfileAppsHostSideTest -->
-    <uses-permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"/>
+    <uses-permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES" />
 
     <!-- permissions required for CTS test - PhoneStateListenerTest -->
     <uses-permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH" />
 
     <!-- Permissions required for granting and logging -->
-    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
-    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
-    <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
-    <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+    <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG" />
+    <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD" />
 
     <!-- Permission required for CTS test - BatterySaverTest -->
-    <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
+    <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" />
 
     <!-- Permission required for CTS test - UiModeManagerTest -->
-    <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/>
-    <uses-permission android:name="android.permission.READ_PROJECTION_STATE"/>
+    <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED" />
+    <uses-permission android:name="android.permission.READ_PROJECTION_STATE" />
 
     <!-- Permission required for CTS tests - UiModeManagerTest, CarModeInCallServiceTest -->
-    <uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION"/>
+    <uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION" />
 
     <!-- Permission required for CTS test - SystemConfigTest -->
-    <uses-permission android:name="android.permission.READ_CARRIER_APP_INFO"/>
+    <uses-permission android:name="android.permission.READ_CARRIER_APP_INFO" />
 
     <!-- Permission required for CTS test - CarModeInCallServiceTest -->
-    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
 
     <!-- Permission requried for CTS test - CellBroadcastIntentsTest -->
-    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS"/>
+    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS" />
 
     <!-- Permission required for CTS test - TetheringManagerTest -->
-    <uses-permission android:name="android.permission.TETHER_PRIVILEGED"/>
+    <uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
 
     <!-- Permission required for CTS test - CtsOsTestCases -->
-    <uses-permission android:name="android.permission.MANAGE_CRATES"/>
+    <uses-permission android:name="android.permission.MANAGE_CRATES" />
 
     <!-- Allows setting brightness from the shell -->
-    <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
+    <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
 
     <!-- Permission required for CTS test - ShortcutManagerUsageTest -->
-    <uses-permission android:name="android.permission.ACCESS_SHORTCUTS"/>
+    <uses-permission android:name="android.permission.ACCESS_SHORTCUTS" />
 
     <!-- Permissions required for CTS test - UsageStatsTest -->
-    <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS"/>
-    <uses-permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"/>
+    <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" />
+    <uses-permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS" />
 
     <!-- Permission needed for CTS test - MusicRecognitionManagerTest -->
     <uses-permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION" />
@@ -442,8 +442,8 @@
     <uses-permission android:name="android.permission.MANAGE_SPEECH_RECOGNITION" />
 
     <!-- Permissions required to test ambient display. -->
-    <uses-permission android:name="android.permission.READ_DREAM_STATE"/>
-    <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/>
+    <uses-permission android:name="android.permission.READ_DREAM_STATE" />
+    <uses-permission android:name="android.permission.WRITE_DREAM_STATE" />
 
     <!-- Permission required for CTS test - CtsLightsManagerTest -->
     <uses-permission android:name="android.permission.CONTROL_DEVICE_LIGHTS" />
@@ -470,7 +470,7 @@
     <uses-permission android:name="android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE" />
 
     <!-- Permission required for testing system audio effect APIs. -->
-    <uses-permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"/>
+    <uses-permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
 
     <!-- Permission required for running networking unit tests -->
     <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
@@ -495,7 +495,7 @@
     <uses-permission android:name="android.permission.TV_INPUT_HARDWARE" />
     <uses-permission android:name="android.permission.TIS_EXTENSION_INTERFACE" />
     <uses-permission android:name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS" />
-    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA"/>
+    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
 
     <!-- Permission needed for CTS test - PrivilegedLocationPermissionTest -->
     <uses-permission android:name="android.permission.LOCATION_HARDWARE" />
@@ -560,14 +560,14 @@
     <uses-permission android:name="android.permission.BIND_CARRIER_SERVICES" />
 
     <!-- Allows overriding the system's device state from the shell -->
-    <uses-permission android:name="android.permission.CONTROL_DEVICE_STATE"/>
+    <uses-permission android:name="android.permission.CONTROL_DEVICE_STATE" />
 
     <!-- Permissions required for CTS tests to close system dialogs -->
     <uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
 
     <!-- Permissions required for CTS test - HideOverlayWindowsTest -->
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
-    <uses-permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY" />
 
     <!-- Permission required for CTS test - CtsHdmiCecHostTestCases -->
     <uses-permission android:name="android.permission.HDMI_CEC" />
@@ -630,21 +630,21 @@
     <uses-permission android:name="android.permission.UPDATE_FONTS" />
 
     <!-- Permission required for Launcher testing - DigitalWellbeingToastTest -->
-    <uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO"/>
+    <uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" />
 
     <!-- Permission required for hotword detection service CTS tests -->
     <uses-permission android:name="android.permission.MANAGE_HOTWORD_DETECTION" />
     <uses-permission android:name="android.permission.BIND_HOTWORD_DETECTION_SERVICE" />
 
     <!-- Permission required for CTS test - CtsVoiceInteractionTestCases -->
-    <uses-permission android:name="android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER"/>
+    <uses-permission android:name="android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER" />
 
     <uses-permission android:name="android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE" />
 
     <!-- Permission required for CTS test - KeyguardLockedStateApiTest -->
     <uses-permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
 
-    <uses-permission android:name="android.permission.MANAGE_APP_HIBERNATION"/>
+    <uses-permission android:name="android.permission.MANAGE_APP_HIBERNATION" />
 
     <!-- Permission required for CTS test - MediaCodecResourceTest -->
     <uses-permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" />
@@ -820,8 +820,8 @@
     <uses-permission android:name="android.permission.DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA" />
     <uses-permission android:name="android.permission.STAGE_HEALTH_CONNECT_REMOTE_DATA" />
 
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
 
     <!-- Permissions required for CTS test - CtsBroadcastRadioTestCases -->
     <uses-permission android:name="android.permission.ACCESS_BROADCAST_RADIO" />
@@ -832,12 +832,17 @@
     <!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
     <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
 
-    <uses-permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE"/>
+    <uses-permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE" />
+    <!-- Permission required for GTS test - GtsAttestationVerificationDeviceSideTestCases -->
+    <uses-permission android:name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" />
+    <!-- Permission required for GTS test - GtsCredentialsTestCases -->
+    <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR" />
 
-    <application android:label="@string/app_label"
-                android:theme="@android:style/Theme.DeviceDefault.DayNight"
-                android:defaultToDeviceProtectedStorage="true"
-                android:directBootAware="true">
+    <application
+        android:label="@string/app_label"
+        android:theme="@android:style/Theme.DeviceDefault.DayNight"
+        android:defaultToDeviceProtectedStorage="true"
+        android:directBootAware="true">
         <provider
             android:name="androidx.core.content.FileProvider"
             android:authorities="com.android.shell"
@@ -860,10 +865,11 @@
             </intent-filter>
         </provider>
 
-        <provider android:name=".HeapDumpProvider"
-                  android:authorities="com.android.shell.heapdump"
-                  android:grantUriPermissions="true"
-                  android:exported="false" />
+        <provider
+            android:name=".HeapDumpProvider"
+            android:authorities="com.android.shell.heapdump"
+            android:grantUriPermissions="true"
+            android:exported="false" />
 
         <activity
             android:name=".BugreportWarningActivity"
@@ -872,13 +878,14 @@
             android:excludeFromRecents="true"
             android:exported="false" />
 
-        <activity android:name=".HeapDumpActivity"
-                  android:theme="@*android:style/Theme.Translucent.NoTitleBar"
-                  android:label="@*android:string/dump_heap_title"
-                  android:finishOnCloseSystemDialogs="true"
-                  android:noHistory="true"
-                  android:excludeFromRecents="true"
-                  android:exported="false" />
+        <activity
+            android:name=".HeapDumpActivity"
+            android:theme="@*android:style/Theme.Translucent.NoTitleBar"
+            android:label="@*android:string/dump_heap_title"
+            android:finishOnCloseSystemDialogs="true"
+            android:noHistory="true"
+            android:excludeFromRecents="true"
+            android:exported="false" />
 
         <receiver
             android:name=".BugreportRequestedReceiver"
@@ -903,7 +910,7 @@
         <receiver
             android:name=".ProfcollectUploadReceiver"
             android:exported="true"
-            android:permission="android.permission.TRIGGER_SHELL_PROFCOLLECT_UPLOAD" >
+            android:permission="android.permission.TRIGGER_SHELL_PROFCOLLECT_UPLOAD">
             <intent-filter>
                 <action android:name="com.android.shell.action.PROFCOLLECT_UPLOAD" />
             </intent-filter>
@@ -912,6 +919,6 @@
         <service
             android:name=".BugreportProgressService"
             android:foregroundServiceType="systemExempted"
-            android:exported="false"/>
+            android:exported="false" />
     </application>
 </manifest>
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 5b5871f..8eb012d 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -43,6 +43,7 @@
         "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 94b3740..4037fd4 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 42a8636..48dd08f 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
deleted file mode 100644
index 9dbb920..0000000
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 8da87feb..0000000
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.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 3417ffd..142fd21 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.systemui.animation.Interpolators.LINEAR
+import com.android.app.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 58ffef2..8e79e3c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -25,6 +25,7 @@
 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 f3d8b17..dd32851 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.systemui.animation.Interpolators
+import com.android.app.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 941a925..8dd2c39 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
@@ -28,9 +28,9 @@
 import android.util.AttributeSet
 import android.util.MathUtils.constrainedMap
 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.log.LogBuffer
diff --git a/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml b/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
index d123caf..cff2839 100644
--- a/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
+++ b/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
@@ -2,11 +2,11 @@
     android:viewportWidth="22" android:width="20.166666dp" xmlns:android="http://schemas.android.com/apk/res/android">
     <group>
         <clip-path android:pathData="M0,0.5h22v11h-22z"/>
-        <path android:fillColor="#231F20" android:pathData="M6.397,9.908H0V11.5H6.397V9.908Z"/>
-        <path android:fillColor="#231F20" android:pathData="M14.199,9.908H7.801V11.5H14.199V9.908Z"/>
-        <path android:fillColor="#231F20" android:pathData="M11.858,0.5H10.142V6.434H11.858V0.5Z"/>
-        <path android:fillColor="#231F20" android:pathData="M8.348,7.129L3.885,2.975L3.823,2.932L2.668,4.003L2.621,4.046L7.084,8.2L7.146,8.243L8.301,7.172L8.348,7.129Z"/>
-        <path android:fillColor="#231F20" android:pathData="M18.224,2.975L18.177,2.932L13.653,7.129L14.807,8.2L14.854,8.243L19.379,4.046L18.224,2.975Z"/>
-        <path android:fillColor="#231F20" android:pathData="M22,9.908H15.603V11.5H22V9.908Z"/>
+        <path android:fillColor="@android:color/white" android:pathData="M6.397,9.908H0V11.5H6.397V9.908Z"/>
+        <path android:fillColor="@android:color/white" android:pathData="M14.199,9.908H7.801V11.5H14.199V9.908Z"/>
+        <path android:fillColor="@android:color/white" android:pathData="M11.858,0.5H10.142V6.434H11.858V0.5Z"/>
+        <path android:fillColor="@android:color/white" android:pathData="M8.348,7.129L3.885,2.975L3.823,2.932L2.668,4.003L2.621,4.046L7.084,8.2L7.146,8.243L8.301,7.172L8.348,7.129Z"/>
+        <path android:fillColor="@android:color/white" android:pathData="M18.224,2.975L18.177,2.932L13.653,7.129L14.807,8.2L14.854,8.243L19.379,4.046L18.224,2.975Z"/>
+        <path android:fillColor="@android:color/white" android:pathData="M22,9.908H15.603V11.5H22V9.908Z"/>
     </group>
 </vector>
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index 441f963..386c9d6 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -14,12 +14,17 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
+
+<!--
+keep split_shade_status_bar height constant to avoid requestLayout calls on each
+frame when animating QS <-> QQS transition
+-->
 <com.android.systemui.util.NoRemeasureMotionLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/split_shade_status_bar"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
+    android:layout_height="@dimen/qs_header_height"
     android:minHeight="@dimen/large_screen_shade_header_min_height"
     android:clickable="false"
     android:focusable="true"
@@ -30,6 +35,14 @@
     app:layoutDescription="@xml/combined_qs_header_scene">
 
     <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/qqs_header_bottom_guideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_begin="@dimen/large_screen_shade_header_min_height"
+        />
+
+    <androidx.constraintlayout.widget.Guideline
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:id="@+id/begin_guide"
diff --git a/packages/SystemUI/res/layout/smart_action_button.xml b/packages/SystemUI/res/layout/smart_action_button.xml
index 488be3a..4e5785d 100644
--- a/packages/SystemUI/res/layout/smart_action_button.xml
+++ b/packages/SystemUI/res/layout/smart_action_button.xml
@@ -29,8 +29,8 @@
         android:textSize="@dimen/smart_reply_button_font_size"
         android:lineSpacingExtra="@dimen/smart_reply_button_line_spacing_extra"
         android:textColor="@color/smart_reply_button_text"
-        android:paddingLeft="@dimen/smart_reply_button_action_padding_left"
-        android:paddingRight="@dimen/smart_reply_button_padding_horizontal"
+        android:paddingStart="@dimen/smart_reply_button_action_padding_left"
+        android:paddingEnd="@dimen/smart_reply_button_padding_horizontal"
         android:drawablePadding="@dimen/smart_action_button_icon_padding"
         android:textStyle="normal"
         android:ellipsize="none"/>
diff --git a/packages/SystemUI/res/layout/smart_reply_button.xml b/packages/SystemUI/res/layout/smart_reply_button.xml
index ddf16e0..b24362f 100644
--- a/packages/SystemUI/res/layout/smart_reply_button.xml
+++ b/packages/SystemUI/res/layout/smart_reply_button.xml
@@ -31,7 +31,7 @@
         android:textSize="@dimen/smart_reply_button_font_size"
         android:lineSpacingExtra="@dimen/smart_reply_button_line_spacing_extra"
         android:textColor="@color/smart_reply_button_text"
-        android:paddingLeft="@dimen/smart_reply_button_padding_horizontal"
-        android:paddingRight="@dimen/smart_reply_button_padding_horizontal"
+        android:paddingStart="@dimen/smart_reply_button_padding_horizontal"
+        android:paddingEnd="@dimen/smart_reply_button_padding_horizontal"
         android:textStyle="normal"
         android:ellipsize="none"/>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index cb8c2a7..db7eb7a 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -207,11 +207,6 @@
     <color name="controls_task_view_bg">#CC191C1D</color>
     <color name="control_popup_dim">#8A000000</color>
 
-    <!-- Keyboard backlight indicator-->
-    <color name="backlight_indicator_step_filled">#F6E388</color>
-    <color name="backlight_indicator_step_empty">#494740</color>
-    <color name="backlight_indicator_background">#32302A</color>
-
     <!-- Docked misalignment message -->
     <color name="misalignment_text_color">#F28B82</color>
 
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1602189..1252695 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -889,4 +889,8 @@
     -->
     <dimen name="shade_swipe_collapse_threshold">0.5</dimen>
     <!-- [END] MULTI SHADE -->
+
+    <!-- Time (in ms) to delay the bouncer views from showing when passive auth may be used for
+    device entry. -->
+    <integer name="primary_bouncer_passive_auth_delay">250</integer>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0aa880f..aff0e80 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -615,6 +615,7 @@
     <dimen name="qs_header_carrier_separator_width">6dp</dimen>
     <dimen name="qs_carrier_margin_width">4dp</dimen>
     <dimen name="qs_footer_icon_size">20dp</dimen>
+    <dimen name="qs_header_height">120dp</dimen>
     <dimen name="qs_header_row_min_height">48dp</dimen>
 
     <dimen name="qs_header_non_clickable_element_height">24dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 499dfa4..eaeaabe8 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -198,6 +198,9 @@
     <item type="id" name="pm_lite"/>
     <item type="id" name="settings_button_container"/>
 
+    <!--Keyboard Backlight Dialog -->
+    <item type="id" name="keyboard_backlight_dialog_container"/>
+
     <item type="id" name="log_access_dialog_allow_button" />
     <item type="id" name="log_access_dialog_deny_button" />
 </resources>
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index 00a0444..1950965 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -28,7 +28,7 @@
             android:layout_height="@dimen/large_screen_shade_header_min_height"
             app:layout_constraintStart_toStartOf="@id/begin_guide"
             app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
             app:layout_constraintEnd_toStartOf="@id/date"
             app:layout_constraintHorizontal_bias="0"
             app:layout_constraintHorizontal_chainStyle="packed"
@@ -62,7 +62,7 @@
             app:layout_constraintStart_toEndOf="@id/date"
             app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
             app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
             app:layout_constraintHorizontal_bias="1"
             app:layout_constraintHorizontal_chainStyle="packed"
             />
@@ -77,7 +77,7 @@
             app:layout_constraintStart_toEndOf="@id/statusIcons"
             app:layout_constraintEnd_toEndOf="@id/end_guide"
             app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
             app:layout_constraintHorizontal_bias="1"
             app:layout_constraintHorizontal_chainStyle="packed"
             />
@@ -105,7 +105,7 @@
             app:layout_constraintStart_toEndOf="@id/date"
             app:layout_constraintEnd_toEndOf="@id/end_guide"
             app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
             app:layout_constraintHorizontal_bias="1"
         />
     </Constraint>
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 62f4f22..a82f0e3 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
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.TITLE
 
 /** Displays security messages for the keyguard bouncer. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 4629e8b..644a9bc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -15,12 +15,13 @@
 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.log.LogBuffer;
 import com.android.systemui.log.LogLevel;
 import com.android.systemui.plugins.ClockController;
+import com.android.systemui.shared.clocks.DefaultClockController;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -46,6 +47,9 @@
 
     public static final int LARGE = 0;
     public static final int SMALL = 1;
+    // compensate for translation of parents subject to device screen
+    // In this case, the translation comes from KeyguardStatusView
+    public int screenOffsetYPadding = 0;
 
     /** Returns a region for the large clock to position itself, based on the given parent. */
     public static Rect getLargeClockRegion(ViewGroup parent) {
@@ -161,8 +165,18 @@
             }
 
             if (mLargeClockFrame.isLaidOut()) {
-                mClock.getLargeClock().getEvents().onTargetRegionChanged(
-                        getLargeClockRegion(mLargeClockFrame));
+                Rect targetRegion = getLargeClockRegion(mLargeClockFrame);
+                if (mClock instanceof DefaultClockController) {
+                    mClock.getLargeClock().getEvents().onTargetRegionChanged(
+                            targetRegion);
+                } else {
+                    mClock.getLargeClock().getEvents().onTargetRegionChanged(
+                            new Rect(
+                                    targetRegion.left,
+                                    targetRegion.top - screenOffsetYPadding,
+                                    targetRegion.right,
+                                    targetRegion.bottom - screenOffsetYPadding));
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 50dac32..d8bf570 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -169,6 +169,16 @@
     }
 
     /**
+     * Used for status view to pass the screen offset from parent view
+     */
+    public void setLockscreenClockY(int clockY) {
+        if (mView.screenOffsetYPadding != clockY) {
+            mView.screenOffsetYPadding = clockY;
+            mView.updateClockTargetRegions();
+        }
+    }
+
+    /**
      * Attach the controller to the view it relates to.
      */
     @Override
@@ -394,13 +404,6 @@
             PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X,
                     x, props, animate);
         }
-
-    }
-
-    void updateKeyguardStatusViewOffset() {
-        // updateClockTargetRegions will call onTargetRegionChanged
-        // which will require the correct translationY property of keyguardStatusView after updating
-        mView.updateClockTargetRegions();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 0394754..58807e4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_APPEAR;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
@@ -32,9 +33,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;
 
 /**
@@ -184,6 +185,7 @@
         }
         mAppearAnimator.setDuration(ANIMATION_DURATION);
         mAppearAnimator.addUpdateListener(animation -> animate(animation.getAnimatedFraction()));
+        mAppearAnimator.addListener(getAnimationListener(CUJ_LOCKSCREEN_PIN_APPEAR));
         mAppearAnimator.start();
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 33bea02..1d7c35d 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 0a91150..b4ddc9a 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 b88d85c..5cc0547 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.systemui.animation.InterpolatorsAndroidX.DECELERATE_QUINT;
+import static com.android.app.animation.InterpolatorsAndroidX.DECELERATE_QUINT;
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
 
 import static java.lang.Integer.max;
@@ -87,6 +87,7 @@
 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,7 +98,6 @@
 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 c9128e5..96ac8ad 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 65a7166..b4f124a 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 0826f8a..794eeda 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;
@@ -215,6 +215,15 @@
     }
 
     /**
+     * Pass top margin from ClockPositionAlgorithm in NotificationPanelViewController
+     * Use for clock view in LS to compensate for top margin to align to the screen
+     * Regardless of translation from AOD and unlock gestures
+     */
+    public void setLockscreenClockY(int clockY) {
+        mKeyguardClockSwitchController.setLockscreenClockY(clockY);
+    }
+
+    /**
      * Set whether the view accessibility importance mode.
      */
     public void setStatusAccessibilityImportance(int mode) {
@@ -230,7 +239,6 @@
      * Update position of the view with an optional animation
      */
     public void updatePosition(int x, int y, float scale, boolean animate) {
-        float oldY = mView.getY();
         setProperty(AnimatableProperty.Y, y, animate);
 
         ClockController clock = mKeyguardClockSwitchController.getClock();
@@ -246,10 +254,6 @@
             setProperty(AnimatableProperty.SCALE_X, 1f, animate);
             setProperty(AnimatableProperty.SCALE_Y, 1f, animate);
         }
-
-        if (oldY != y) {
-            mKeyguardClockSwitchController.updateKeyguardStatusViewOffset();
-        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 7d7b276..c1f70fb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2884,7 +2884,19 @@
         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
     }
 
+    /**
+     * If the current state of the device allows for triggering active unlock. This does not
+     * include active unlock availability.
+     */
+    public boolean canTriggerActiveUnlockBasedOnDeviceState() {
+        return shouldTriggerActiveUnlock(/* shouldLog */ false);
+    }
+
     private boolean shouldTriggerActiveUnlock() {
+        return shouldTriggerActiveUnlock(/* shouldLog */ true);
+    }
+
+    private boolean shouldTriggerActiveUnlock(boolean shouldLog) {
         // Triggers:
         final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
         final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mAlternateBouncerShowing
@@ -2914,19 +2926,21 @@
                         && !mKeyguardGoingAway
                         && !mSecureCameraLaunched;
 
-        // Aggregate relevant fields for debug logging.
-        logListenerModelData(
-                new KeyguardActiveUnlockModel(
-                        System.currentTimeMillis(),
-                        user,
-                        shouldTriggerActiveUnlock,
-                        awakeKeyguard,
-                        mAuthInterruptActive,
-                        fpLockedOut,
-                        primaryAuthRequired,
-                        mSwitchingUser,
-                        triggerActiveUnlockForAssistant,
-                        userCanDismissLockScreen));
+        if (shouldLog) {
+            // Aggregate relevant fields for debug logging.
+            logListenerModelData(
+                    new KeyguardActiveUnlockModel(
+                            System.currentTimeMillis(),
+                            user,
+                            shouldTriggerActiveUnlock,
+                            awakeKeyguard,
+                            mAuthInterruptActive,
+                            fpLockedOut,
+                            primaryAuthRequired,
+                            mSwitchingUser,
+                            triggerActiveUnlockForAssistant,
+                            userCanDismissLockScreen));
+        }
 
         return shouldTriggerActiveUnlock;
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index d8568ba..61af722 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.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.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 c6c7113..7d76f12 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -37,7 +37,7 @@
 
 import androidx.annotation.StyleRes;
 
-import com.android.systemui.animation.Interpolators;
+import com.android.app.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 4aeab97..4557b34 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
@@ -37,9 +37,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/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index de82ca0..c1871e0 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.systemui.animation.Interpolators
+import com.android.app.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 48805be..76086df 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.systemui.animation.Interpolators
+import com.android.app.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 aa94ad9..99d4662 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;
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
index d6f0b59..d491975 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 0002ae9..2aac056 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 b386bc9..ce85124 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 55f6d07..6f0f633 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.systemui.animation.Interpolators
+import com.android.app.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 b007134..5ede16d 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.systemui.animation.Interpolators
+import com.android.app.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 ef7dcb7..1dbafc6 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.systemui.animation.Interpolators
+import com.android.app.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 ba8e60a..52db4ab 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 935de02..9f5669f 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 e2d36dc..9292bd7 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 11ef749..7bf8f4d 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 8d0edf8..b447d66 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.systemui.animation.Interpolators
+import com.android.app.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 6a9aaf8..e6361f4 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.systemui.animation.Interpolators
+import com.android.app.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 fa36eee..1461135 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.systemui.animation.Interpolators
+import com.android.app.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/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 89c45d7..17cf808 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -69,6 +69,7 @@
 import com.android.systemui.qs.QSFragmentStartableModule;
 import com.android.systemui.qs.footer.dagger.FooterActionsModule;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.retail.dagger.RetailModeModule;
 import com.android.systemui.screenrecord.ScreenRecordModule;
 import com.android.systemui.screenshot.dagger.ScreenshotModule;
 import com.android.systemui.security.data.repository.SecurityRepositoryModule;
@@ -179,6 +180,7 @@
             PrivacyModule.class,
             QRCodeScannerModule.class,
             QSFragmentStartableModule.class,
+            RetailModeModule.class,
             ScreenshotModule.class,
             SensorModule.class,
             SecurityRepositoryModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index df46e07e..c5e7e0d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -25,8 +25,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 15a32d2..c22019e 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/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 1fafa3d..d1d53cc 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -702,6 +702,5 @@
 
     // TODO(b/278761837): Tracking Bug
     @JvmField
-    val USE_NEW_ACTIVITY_STARTER = unreleasedFlag(2801, name = "use_new_activity_starter",
-            teamfood = true)
+    val USE_NEW_ACTIVITY_STARTER = releasedFlag(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 f64ed60..2807107 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -97,6 +97,7 @@
 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;
@@ -116,7 +117,6 @@
 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/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index 2ef5e19..6bc763c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyboard.backlight.ui.view
 
+import android.annotation.AttrRes
 import android.annotation.ColorInt
 import android.app.Dialog
 import android.content.Context
@@ -31,6 +32,7 @@
 import android.widget.LinearLayout
 import android.widget.LinearLayout.LayoutParams
 import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
+import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.util.children
 
@@ -38,7 +40,7 @@
     context: Context,
     initialCurrentLevel: Int,
     initialMaxLevel: Int,
-) : Dialog(context) {
+) : Dialog(context, R.style.Theme_SystemUI_Dialog) {
 
     private data class RootProperties(
         val cornerRadius: Float,
@@ -69,9 +71,14 @@
     private lateinit var rootProperties: RootProperties
     private lateinit var iconProperties: BacklightIconProperties
     private lateinit var stepProperties: StepViewProperties
-    @ColorInt var filledRectangleColor: Int = 0
-    @ColorInt var emptyRectangleColor: Int = 0
-    @ColorInt var backgroundColor: Int = 0
+    @ColorInt
+    var filledRectangleColor = getColorFromStyle(com.android.internal.R.attr.materialColorPrimary)
+    @ColorInt
+    var emptyRectangleColor =
+        getColorFromStyle(com.android.internal.R.attr.materialColorOutlineVariant)
+    @ColorInt
+    var backgroundColor = getColorFromStyle(com.android.internal.R.attr.materialColorSurfaceBright)
+    @ColorInt var iconColor = getColorFromStyle(com.android.internal.R.attr.materialColorOnPrimary)
 
     init {
         currentLevel = initialCurrentLevel
@@ -90,9 +97,6 @@
 
     private fun updateResources() {
         context.resources.apply {
-            filledRectangleColor = getColor(R.color.backlight_indicator_step_filled, context.theme)
-            emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty, context.theme)
-            backgroundColor = getColor(R.color.backlight_indicator_background, context.theme)
             rootProperties =
                 RootProperties(
                     cornerRadius =
@@ -126,6 +130,11 @@
         }
     }
 
+    @ColorInt
+    fun getColorFromStyle(@AttrRes colorId: Int): Int {
+        return Utils.getColorAttrDefaultColor(context, colorId)
+    }
+
     fun updateState(current: Int, max: Int, forceRefresh: Boolean = false) {
         if (maxLevel != max || forceRefresh) {
             maxLevel = max
@@ -159,6 +168,7 @@
     private fun buildRootView(): LinearLayout {
         val linearLayout =
             LinearLayout(context).apply {
+                id = R.id.keyboard_backlight_dialog_container
                 orientation = LinearLayout.HORIZONTAL
                 layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
                 setPadding(
@@ -214,6 +224,7 @@
     private fun createBacklightIconView(): ImageView {
         return ImageView(context).apply {
             setImageResource(R.drawable.ic_keyboard_backlight)
+            setColorFilter(iconColor)
             layoutParams =
                 FrameLayout.LayoutParams(iconProperties.width, iconProperties.height).apply {
                     gravity = Gravity.CENTER
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 2925d8d..9844ca0 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.systemui.animation.Interpolators
+import com.android.app.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 1a126d7..2854de0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -98,6 +98,7 @@
 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;
@@ -121,7 +122,6 @@
 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/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
index ef8b401..ee0eb2d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -46,8 +46,6 @@
         impl: SystemUIKeyguardFaceAuthInteractor
     ): KeyguardFaceAuthInteractor
 
-    @Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
-
     companion object {
         @Provides
         @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index f27f899..e7b9af6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -45,4 +45,6 @@
 
     @Binds
     fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
+
+    @Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
index d90f328..74c5520 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
@@ -28,7 +28,9 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
@@ -40,6 +42,9 @@
 interface TrustRepository {
     /** Flow representing whether the current user is trusted. */
     val isCurrentUserTrusted: Flow<Boolean>
+
+    /** Flow representing whether active unlock is available for the current user. */
+    val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean>
 }
 
 @SysUISingleton
@@ -89,11 +94,13 @@
             }
             .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
 
-    override val isCurrentUserTrusted: Flow<Boolean>
-        get() =
-            combine(trust, userRepository.selectedUserInfo, ::Pair)
-                .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
-                .distinctUntilChanged()
-                .onEach { logger.isCurrentUserTrusted(it) }
-                .onStart { emit(false) }
+    override val isCurrentUserTrusted: Flow<Boolean> =
+        combine(trust, userRepository.selectedUserInfo, ::Pair)
+            .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
+            .distinctUntilChanged()
+            .onEach { logger.isCurrentUserTrusted(it) }
+            .onStart { emit(false) }
+
+    // TODO: Implement based on TrustManager callback b/267322286
+    override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> = MutableStateFlow(true)
 }
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 e6568f2..cde67f9 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.systemui.animation.Interpolators
+import com.android.app.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 c2d139c..7e9cbc1 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.systemui.animation.Interpolators
+import com.android.app.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 86f65dde..aca4019 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.systemui.animation.Interpolators
+import com.android.app.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 3beac0b..fc7bfb4 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.systemui.animation.Interpolators
+import com.android.app.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 b5bcd45..39c630b 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.systemui.animation.Interpolators
+import com.android.app.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 87f3164..0505d37 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.systemui.animation.Interpolators
+import com.android.app.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 c0d5abc..47846d1 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.systemui.animation.Interpolators
+import com.android.app.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 a681c43..bc55bd4 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/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 970d004..110bcd7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -32,14 +32,16 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.shared.system.SysUiStatsLog
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -56,28 +58,21 @@
 class PrimaryBouncerInteractor
 @Inject
 constructor(
-    private val repository: KeyguardBouncerRepository,
-    private val primaryBouncerView: BouncerView,
-    @Main private val mainHandler: Handler,
-    private val keyguardStateController: KeyguardStateController,
-    private val keyguardSecurityModel: KeyguardSecurityModel,
-    private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
-    private val falsingCollector: FalsingCollector,
-    private val dismissCallbackRegistry: DismissCallbackRegistry,
-    private val context: Context,
-    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    keyguardBypassController: KeyguardBypassController,
+        private val repository: KeyguardBouncerRepository,
+        private val primaryBouncerView: BouncerView,
+        @Main private val mainHandler: Handler,
+        private val keyguardStateController: KeyguardStateController,
+        private val keyguardSecurityModel: KeyguardSecurityModel,
+        private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
+        private val falsingCollector: FalsingCollector,
+        private val dismissCallbackRegistry: DismissCallbackRegistry,
+        private val context: Context,
+        private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+        private val trustRepository: TrustRepository,
+        private val featureFlags: FeatureFlags,
 ) {
-    /** Whether we want to wait for face auth. */
-    private val primaryBouncerFaceDelay =
-        keyguardStateController.isFaceAuthEnabled &&
-            !keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                KeyguardUpdateMonitor.getCurrentUser()
-            ) &&
-            !needsFullscreenBouncer() &&
-            keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) &&
-            !keyguardBypassController.bypassEnabled
-
+    private val passiveAuthBouncerDelay = context.resources.getInteger(
+            R.integer.primary_bouncer_passive_auth_delay).toLong()
     /** Runnable to show the primary bouncer. */
     val showRunnable = Runnable {
         repository.setPrimaryShow(true)
@@ -160,8 +155,9 @@
         }
 
         repository.setPrimaryShowingSoon(true)
-        if (primaryBouncerFaceDelay) {
-            mainHandler.postDelayed(showRunnable, 1200L)
+        if (usePrimaryBouncerPassiveAuthDelay()) {
+            Log.d(TAG, "delay bouncer, passive auth may succeed")
+            mainHandler.postDelayed(showRunnable, passiveAuthBouncerDelay)
         } else {
             DejankUtils.postAfterTraversal(showRunnable)
         }
@@ -377,6 +373,17 @@
         return repository.primaryBouncerShow.value
     }
 
+    /** Whether we want to wait to show the bouncer in case passive auth succeeds. */
+    private fun usePrimaryBouncerPassiveAuthDelay(): Boolean {
+        val canRunFaceAuth = keyguardStateController.isFaceAuthEnabled &&
+                keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)
+        val canRunActiveUnlock = trustRepository.isCurrentUserActiveUnlockAvailable.value &&
+                keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()
+        return featureFlags.isEnabled(Flags.DELAY_BOUNCER) &&
+                !needsFullscreenBouncer() &&
+                (canRunFaceAuth || canRunActiveUnlock)
+    }
+
     companion object {
         private const val TAG = "PrimaryBouncerInteractor"
     }
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 38b9d50..9d7477c 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.systemui.animation.Interpolators.LINEAR
+import com.android.app.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 d96609c..c8d37a1 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 8d6545a4..2c9a9b3 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.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.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 f16827d..c135786 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.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.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 bc9dc4f..c6187dd 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.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.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 a60665a..d3ea89c 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.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.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 ddce516..6845c55 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.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.app.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 df93d23..68810f9 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.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.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/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index aec7b5f..c41f82b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -111,7 +111,7 @@
         if (featureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
             component.emptyStateProvider
         } else {
-            super.createBlockerEmptyStateProvider()
+            object : EmptyStateProvider {}
         }
 
     override fun createListController(userHandle: UserHandle): ResolverListController =
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index 28adfbb..c9c2ea2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -79,6 +79,9 @@
     // Indicates if user must review already-granted consent that the MediaProjection app is
     // attempting to re-use.
     private boolean mReviewGrantedConsentRequired = false;
+    // Indicates if the user has consented to record, but is continuing in another activity to
+    // select a particular task to capture.
+    private boolean mUserSelectingTask = false;
 
     @Inject
     public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
@@ -296,6 +299,7 @@
                 // Start activity from the current foreground user to avoid creating a separate
                 // SystemUI process without access to recent tasks because it won't have
                 // WM Shell running inside.
+                mUserSelectingTask = true;
                 startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser()));
             }
         } catch (RemoteException e) {
@@ -316,7 +320,10 @@
     @Override
     public void finish() {
         // Default to cancelling recording when user needs to review consent.
-        finish(RECORD_CANCEL, /* projection= */ null);
+        // Don't send cancel if the user has moved on to the next activity.
+        if (!mUserSelectingTask) {
+            finish(RECORD_CANCEL, /* projection= */ null);
+        }
     }
 
     private void finish(@ReviewGrantedConsentResult int consentResult,
@@ -328,7 +335,7 @@
 
     private void onDialogDismissedOrCancelled(DialogInterface dialogInterface) {
         if (!isFinishing()) {
-            finish(RECORD_CANCEL, /* projection= */ null);
+            finish();
         }
     }
 
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 37d956b..e38abc2 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 3669493..b46ebb2 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 dd5c2bf..937a618 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 40027a1..f9d3094 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,6 +72,7 @@
 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;
@@ -82,7 +83,6 @@
 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 49e1665..fe8ebaf 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 e9b2cf2..583c626 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 78082c3..77ff036 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.systemui.animation.Interpolators
+import com.android.app.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/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index 219629b..1d8fe72 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -19,7 +19,6 @@
 import android.content.ComponentName
 import android.os.UserHandle
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
 import javax.inject.Inject
@@ -59,13 +58,7 @@
      * Removes all recent tasks that are different from the profile of the host app to avoid any
      * cross-profile sharing
      */
-    private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> =
-        if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
-            // TODO(b/263950746): filter tasks based on the enterprise policies
-            this
-        } else {
-            filter { UserHandle.of(it.userId) == hostUserHandle }
-        }
+    private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> = this
 
     private fun List<RecentTask>.filterAppSelector(): List<RecentTask> = filter {
         // Only take tasks that is not the app selector
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 94f01b8..146b5f5 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 0218016..10084bd 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.systemui.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.LINEAR;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -24,9 +24,6 @@
 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 590efbb..ff22398 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 a7aac5a..463c79c 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.systemui.animation.Interpolators;
+import com.android.app.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/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index 0d29a1a..85f557b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -27,6 +27,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.retail.domain.interactor.RetailModeInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.ViewController;
 
@@ -45,18 +46,22 @@
     private final View mEditButton;
     private final FalsingManager mFalsingManager;
     private final ActivityStarter mActivityStarter;
+    private final RetailModeInteractor mRetailModeInteractor;
 
     @Inject
     QSFooterViewController(QSFooterView view,
             UserTracker userTracker,
             FalsingManager falsingManager,
             ActivityStarter activityStarter,
-            QSPanelController qsPanelController) {
+            QSPanelController qsPanelController,
+            RetailModeInteractor retailModeInteractor
+    ) {
         super(view);
         mUserTracker = userTracker;
         mQsPanelController = qsPanelController;
         mFalsingManager = falsingManager;
         mActivityStarter = activityStarter;
+        mRetailModeInteractor = retailModeInteractor;
 
         mBuildText = mView.findViewById(R.id.build);
         mPageIndicator = mView.findViewById(R.id.footer_page_indicator);
@@ -96,6 +101,8 @@
     @Override
     public void setVisibility(int visibility) {
         mView.setVisibility(visibility);
+        mEditButton
+                .setVisibility(mRetailModeInteractor.isInRetailMode() ? View.GONE : View.VISIBLE);
         mEditButton.setClickable(visibility == View.VISIBLE);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index fd3f701..b8c2fad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -44,10 +44,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;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 595b29a9..3b2362f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -20,6 +20,7 @@
 import android.content.res.Resources
 import android.database.ContentObserver
 import android.provider.Settings
+import com.android.systemui.R
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -27,12 +28,16 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.retail.data.repository.RetailModeRepository
 import com.android.systemui.util.settings.SecureSettings
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -84,6 +89,9 @@
  * [Settings.Secure.QS_TILES].
  *
  * All operations against [Settings] will be performed in a background thread.
+ *
+ * If the device is in retail mode, the tiles are fixed to the value of
+ * [R.string.quick_settings_tiles_retail_mode].
  */
 @SysUISingleton
 class TileSpecSettingsRepository
@@ -92,9 +100,31 @@
     private val secureSettings: SecureSettings,
     @Main private val resources: Resources,
     private val logger: QSPipelineLogger,
+    private val retailModeRepository: RetailModeRepository,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) : TileSpecRepository {
+
+    private val retailModeTiles by lazy {
+        resources
+            .getString(R.string.quick_settings_tiles_retail_mode)
+            .split(DELIMITER)
+            .map(TileSpec::create)
+            .filter { it !is TileSpec.Invalid }
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
     override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
+        return retailModeRepository.retailMode.flatMapLatest { inRetailMode ->
+            if (inRetailMode) {
+                logger.logUsingRetailTiles()
+                flowOf(retailModeTiles)
+            } else {
+                settingsTiles(userId)
+            }
+        }
+    }
+
+    private fun settingsTiles(userId: Int): Flow<List<TileSpec>> {
         return conflatedCallbackFlow {
                 val observer =
                     object : ContentObserver(null) {
@@ -157,6 +187,10 @@
     }
 
     private suspend fun storeTiles(@UserIdInt forUser: Int, tiles: List<TileSpec>) {
+        if (retailModeRepository.inRetailMode) {
+            // No storing tiles when in retail mode
+            return
+        }
         val toStore =
             tiles
                 .filter { it !is TileSpec.Invalid }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index b564334..ff7d206 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -120,6 +120,10 @@
         )
     }
 
+    fun logUsingRetailTiles() {
+        tileListLogBuffer.log(TILE_LIST_TAG, LogLevel.DEBUG, {}, { "Using retail tiles" })
+    }
+
     /** Reasons for destroying an existing tile. */
     enum class TileDestroyedReason(val readable: String) {
         TILE_REMOVED("Tile removed from  current set"),
diff --git a/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt b/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt
new file mode 100644
index 0000000..e863949
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.retail.dagger
+
+import com.android.systemui.retail.data.repository.RetailModeRepository
+import com.android.systemui.retail.data.repository.RetailModeSettingsRepository
+import com.android.systemui.retail.domain.interactor.RetailModeInteractor
+import com.android.systemui.retail.domain.interactor.RetailModeInteractorImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+abstract class RetailModeModule {
+
+    @Binds
+    abstract fun bindsRetailModeRepository(impl: RetailModeSettingsRepository): RetailModeRepository
+
+    @Binds
+    abstract fun bindsRetailModeInteractor(impl: RetailModeInteractorImpl): RetailModeInteractor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt b/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
new file mode 100644
index 0000000..3c0aa38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.retail.data.repository
+
+import android.database.ContentObserver
+import android.provider.Settings
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.settings.GlobalSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository to track if the device is in Retail mode */
+interface RetailModeRepository {
+    /** Flow of whether the device is currently in retail mode. */
+    val retailMode: StateFlow<Boolean>
+
+    /** Last value of whether the device is in retail mode. */
+    val inRetailMode: Boolean
+        get() = retailMode.value
+}
+
+/**
+ * Tracks [Settings.Global.DEVICE_DEMO_MODE].
+ *
+ * @see UserManager.isDeviceInDemoMode
+ */
+@SysUISingleton
+class RetailModeSettingsRepository
+@Inject
+constructor(
+    globalSettings: GlobalSettings,
+    @Background backgroundDispatcher: CoroutineDispatcher,
+    @Application scope: CoroutineScope,
+) : RetailModeRepository {
+    override val retailMode =
+        conflatedCallbackFlow {
+                val observer =
+                    object : ContentObserver(null) {
+                        override fun onChange(selfChange: Boolean) {
+                            trySend(Unit)
+                        }
+                    }
+
+                globalSettings.registerContentObserver(RETAIL_MODE_SETTING, observer)
+
+                awaitClose { globalSettings.unregisterContentObserver(observer) }
+            }
+            .onStart { emit(Unit) }
+            .map { globalSettings.getInt(RETAIL_MODE_SETTING, 0) != 0 }
+            .flowOn(backgroundDispatcher)
+            .stateIn(scope, SharingStarted.Eagerly, false)
+
+    companion object {
+        private const val RETAIL_MODE_SETTING = Settings.Global.DEVICE_DEMO_MODE
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
new file mode 100644
index 0000000..eea452c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.retail.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.retail.data.repository.RetailModeRepository
+import javax.inject.Inject
+
+/** Interactor to determine if the device is currently in retail mode */
+interface RetailModeInteractor {
+    /** Whether the device is currently in retail mode */
+    val isInRetailMode: Boolean
+}
+
+@SysUISingleton
+class RetailModeInteractorImpl
+@Inject
+constructor(
+    private val repository: RetailModeRepository,
+) : RetailModeInteractor {
+    override val isInRetailMode: Boolean
+        get() = repository.inRetailMode
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 137a99ef..926ede9 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,6 +88,7 @@
 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;
@@ -111,7 +112,6 @@
 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;
@@ -1513,6 +1513,8 @@
                 mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
                 mKeyguardStatusViewController.isClockTopAligned());
         mClockPositionAlgorithm.run(mClockPositionResult);
+        mKeyguardStatusViewController.setLockscreenClockY(
+                mClockPositionAlgorithm.getExpandedPreferredClockY());
         mKeyguardBottomAreaInteractor.setClockPosition(
                 mClockPositionResult.clockX, mClockPositionResult.clockY);
         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index fb7c5c2..31b361f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -18,7 +18,6 @@
 
 import android.view.View
 import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.view.WindowInsets
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.widget.ConstraintSet
@@ -62,6 +61,7 @@
     private var isQSCustomizing = false
     private var isQSCustomizerAnimating = false
 
+    private var shadeHeaderHeight = 0
     private var largeScreenShadeHeaderHeight = 0
     private var largeScreenShadeHeaderActive = false
     private var notificationsBottomMargin = 0
@@ -146,6 +146,8 @@
                 R.dimen.notification_panel_margin_bottom)
         largeScreenShadeHeaderHeight =
                 resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
+        shadeHeaderHeight =
+                resources.getDimensionPixelSize(R.dimen.qs_header_height)
         panelMarginHorizontal = resources.getDimensionPixelSize(
                 R.dimen.notification_panel_margin_horizontal)
         topMargin = if (largeScreenShadeHeaderActive) {
@@ -245,7 +247,7 @@
         if (largeScreenShadeHeaderActive) {
             constraintSet.constrainHeight(R.id.split_shade_status_bar, largeScreenShadeHeaderHeight)
         } else {
-            constraintSet.constrainHeight(R.id.split_shade_status_bar, WRAP_CONTENT)
+            constraintSet.constrainHeight(R.id.split_shade_status_bar, shadeHeaderHeight)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index a1fa8fbf..abdd1a9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -50,6 +50,7 @@
 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;
@@ -59,7 +60,6 @@
 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 86ae4ec..f080d3d 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 63179da..c1ebf12 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 54b341f..1a32d70 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/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 9421524..823bb35 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.systemui.animation.Interpolators
+import com.android.app.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 7f016f3..b61f243 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.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.biometrics.UdfpsKeyguardViewController
 import com.android.systemui.classifier.Classifier
 import com.android.systemui.classifier.FalsingCollector
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 72ae16e..fb88a96 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 a3bd247..0e20df6 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.systemui.animation.Interpolators
+import com.android.app.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 49c7950..9d7f3be 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 821a172..fc6eaa8 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.systemui.animation.Interpolators
+import com.android.app.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 575f354..f1e51e2 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.systemui.animation.Interpolators
+import com.android.app.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 572c0e0..3d574ca 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.systemui.animation.Interpolators
+import com.android.app.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 7755003..91c08a0 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 79d01b4a..d6a14604 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -42,6 +42,7 @@
 
 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;
@@ -49,7 +50,6 @@
 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 2fa27ee..67ab060 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 bfc4e9c..eddb683 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.systemui.animation.Interpolators
+import com.android.app.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 0446165..b09b9f4 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 c22dbf6..785e65d 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.systemui.animation.Interpolators
+import com.android.app.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 c22cd1b..5a14200 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 3fc7b13..a045698 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 fe0b28d..9ba2199 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.systemui.animation.Interpolators
-import com.android.systemui.animation.InterpolatorsAndroidX
+import com.android.app.animation.Interpolators
+import com.android.app.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 5d07cac..57d20246 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.systemui.animation.Interpolators;
+import com.android.app.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 9f9fba4..90eb630 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 dc16274..16f1a45 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.systemui.animation.Interpolators
+import com.android.app.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 285dd97..8af488e 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 1dc58b5..ba8a5f3 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,6 +64,7 @@
 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;
@@ -72,7 +73,6 @@
 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 b56bae1..7a2bee9 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 3e01dd3..f0e15c2 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/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 4522e41..b4bfded 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -966,7 +966,7 @@
 
             @Override
             public ApplicationInfo getApplicationInfo() {
-                ApplicationInfo applicationInfo = super.getApplicationInfo();
+                ApplicationInfo applicationInfo = new ApplicationInfo(super.getApplicationInfo());
                 applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_RTL;
                 return applicationInfo;
             }
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 f21db0b..9bc0333 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.systemui.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.app.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 596bdc0..047db20 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/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 8a50f2f..99a7755 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.systemui.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.app.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 bafc474..5a129fc 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 5f4c926..d5d7f75 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 5aaf63f..b24cec1 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 2c59c2e..ef5e86f06 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 7f3381c..d73bbeb 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 0b435fe..9a33a94 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.systemui.animation.Interpolators;
+import com.android.app.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 edff877..cf051fb 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,6 +75,7 @@
 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;
@@ -86,7 +87,6 @@
 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;
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 ee72943..f07dd00 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 d07da38..f4605be 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/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
index 9dce332..4590712 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/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 90a6d0f..561bd91 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;
 
@@ -239,7 +239,11 @@
         }
     }
 
-    private int getExpandedPreferredClockY() {
+    /**
+     * give the static topMargin, used for lockscreen clocks to get the initial translationY
+     * to do counter translation
+     */
+    public int getExpandedPreferredClockY() {
         if (mIsSplitShade) {
             return mSplitShadeTargetTopMargin;
         } else {
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 9d30cb4..61c1cc8 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 13566ef..720eeba 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 12ed71b..4d716c2 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.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 6bf5443..7bc4fc3 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 cc4f901..46a2457 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 55dc188..560ea8a 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 006a029d..bef422c 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 5e5317d7..07a6d0a 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.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
 
 public class SettingsButton extends AlphaOptimizedImageView {
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 8fa803e..cdf6652 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.systemui.animation.Interpolators
+import com.android.app.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 620d282..5af8932 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
@@ -40,10 +40,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 4dd63be..e1ec94f 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 928e011..66b5256 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -31,6 +31,7 @@
 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;
@@ -38,7 +39,6 @@
 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 850a4b4..363b06a 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 403a7e8..e311bad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -73,6 +73,7 @@
 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;
@@ -80,7 +81,6 @@
 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/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index b563d86..21d0338 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -311,7 +311,7 @@
                             setBounds(0, 0, newIconSize, newIconSize)
                         }
                 // Add the action icon to the Smart Action button.
-                setCompoundDrawables(iconDrawable, null, null, null)
+                setCompoundDrawablesRelative(iconDrawable, null, null, null)
 
                 val onClickListener = View.OnClickListener {
                     onSmartActionClick(entry, smartActions, actionIndex, action)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 9e88ceb..fb6ba85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -588,15 +588,15 @@
     }
 
     /**
-     * Returns the combined width of the left drawable (the action icon) and the padding between the
-     * drawable and the button text.
+     * Returns the combined width of the start drawable (the action icon) and the padding between
+     * the drawable and the button text.
      */
-    private int getLeftCompoundDrawableWidthWithPadding(Button button) {
-        Drawable[] drawables = button.getCompoundDrawables();
-        Drawable leftDrawable = drawables[0];
-        if (leftDrawable == null) return 0;
+    private int getStartCompoundDrawableWidthWithPadding(Button button) {
+        Drawable[] drawables = button.getCompoundDrawablesRelative();
+        Drawable startDrawable = drawables[0];
+        if (startDrawable == null) return 0;
 
-        return leftDrawable.getBounds().width() + button.getCompoundDrawablePadding();
+        return startDrawable.getBounds().width() + button.getCompoundDrawablePadding();
     }
 
     private int squeezeButtonToTextWidth(Button button, int heightMeasureSpec, int textWidth) {
@@ -605,8 +605,8 @@
         // Re-measure the squeezed smart reply button.
         clearLayoutLineCount(button);
         final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(
-                button.getPaddingLeft() + button.getPaddingRight() + textWidth
-                      + getLeftCompoundDrawableWidthWithPadding(button), MeasureSpec.AT_MOST);
+                button.getPaddingStart() + button.getPaddingEnd() + textWidth
+                      + getStartCompoundDrawableWidthWithPadding(button), MeasureSpec.AT_MOST);
         button.measure(widthMeasureSpec, heightMeasureSpec);
         if (button.getLayout() == null) {
             Log.wtf(TAG, "Button layout is null after measure.");
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 1612388..46954b5 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.systemui.animation.Interpolators
+import com.android.app.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 e819f94..4fbbc89 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/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
index f0fa779..58f2246 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
+++ b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
@@ -51,6 +51,7 @@
                 Pair.create("surface_variant", MDC.surfaceVariant()),
                 Pair.create("on_surface_variant", MDC.onSurfaceVariant()),
                 Pair.create("outline", MDC.outline()),
+                Pair.create("outline_variant", MDC.outlineVariant()),
                 Pair.create("error", MDC.error()),
                 Pair.create("on_error", MDC.onError()),
                 Pair.create("error_container", MDC.errorContainer()),
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 43d15bc..4b73d61 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -659,6 +659,10 @@
                     == mColorScheme.getNeutral1().getS500()
                     && res.getColor(android.R.color.system_neutral2_500, theme)
                     == mColorScheme.getNeutral2().getS500()
+                    && res.getColor(android.R.color.system_outline_variant_dark, theme)
+                    == MaterialDynamicColors.outlineVariant().getArgb(mDynamicSchemeDark)
+                    && res.getColor(android.R.color.system_outline_variant_light, theme)
+                    == MaterialDynamicColors.outlineVariant().getArgb(mDynamicSchemeLight)
                     && res.getColor(android.R.color.system_primary_container_dark, theme)
                     == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeDark)
                     && res.getColor(android.R.color.system_primary_container_light, theme)
diff --git a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto
index eb23b9d..d940a6b 100644
--- a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto
+++ b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto
@@ -16,8 +16,6 @@
 
 syntax = "proto2";
 
-import "frameworks/base/libs/WindowManager/Shell/proto/wm_shell_trace.proto";
-
 package com.android.systemui.tracing;
 
 option java_multiple_files = true;
@@ -25,7 +23,6 @@
 message SystemUiTraceProto {
 
     optional EdgeBackGestureHandlerProto edge_back_gesture_handler = 1;
-    optional com.android.wm.shell.WmShellTraceProto wm_shell = 2;
 }
 
 message EdgeBackGestureHandlerProto {
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 5d80292..db4ab7ed 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.systemui.animation.Interpolators
+import com.android.app.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 77210b7..91078dc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -109,6 +109,7 @@
 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;
@@ -119,7 +120,6 @@
 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/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index e60f9b6..5144d19 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -362,9 +362,6 @@
 
     @Override
     public void writeToProto(SystemUiTraceProto proto) {
-        if (proto.wmShell == null) {
-            proto.wmShell = new WmShellTraceProto();
-        }
         // Dump to WMShell proto here
         // TODO: Figure out how we want to synchronize while dumping to proto
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
deleted file mode 100644
index 2c680be..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.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 6ab54a3..da9ceb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -19,6 +19,7 @@
 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/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 6e37ee7..a361bbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -68,7 +68,6 @@
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
 
-@Ignore("b/279650412")
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
@@ -281,6 +280,7 @@
         assertThat(authContainer!!.parent).isNull()
     }
 
+    @Ignore("b/279650412")
     @Test
     fun testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
         val container = initializeFingerprintContainer(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 8bf32cf..8f6017c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -21,22 +21,23 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
+import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.RoboPilotTest
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
@@ -94,7 +95,8 @@
                 mock(DismissCallbackRegistry::class.java),
                 context,
                 mKeyguardUpdateMonitor,
-                mock(KeyguardBypassController::class.java),
+                mock(TrustRepository::class.java),
+                FakeFeatureFlags(),
             )
         mAlternateBouncerInteractor =
             AlternateBouncerInteractor(
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 d0bfaa9..5afc405 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,8 +23,8 @@
 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.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/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 5da1a84..e261982 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -35,12 +35,12 @@
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -100,7 +100,8 @@
                     mock(DismissCallbackRegistry::class.java),
                     context,
                     keyguardUpdateMonitor,
-                    mock(KeyguardBypassController::class.java),
+                    mock(TrustRepository::class.java),
+                    FakeFeatureFlags(),
                 ),
                 AlternateBouncerInteractor(
                     mock(StatusBarStateController::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 24a47b0..eed1e739 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import android.os.Looper
+import android.hardware.biometrics.BiometricSourceType
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.TestableResources
@@ -28,15 +28,17 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.BouncerViewDelegate
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
@@ -67,16 +69,23 @@
     @Mock private lateinit var mPrimaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
-    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    private val mainHandler = FakeHandler(Looper.getMainLooper())
+    private lateinit var mainHandler: FakeHandler
     private lateinit var underTest: PrimaryBouncerInteractor
     private lateinit var resources: TestableResources
+    private lateinit var trustRepository: FakeTrustRepository
+    private lateinit var featureFlags: FakeFeatureFlags
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
+            .thenReturn(KeyguardSecurityModel.SecurityMode.PIN)
+
         DejankUtils.setImmediate(true)
+        mainHandler = FakeHandler(android.os.Looper.getMainLooper())
+        trustRepository = FakeTrustRepository()
+        featureFlags = FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, false) }
         underTest =
             PrimaryBouncerInteractor(
                 repository,
@@ -89,7 +98,8 @@
                 dismissCallbackRegistry,
                 context,
                 keyguardUpdateMonitor,
-                keyguardBypassController,
+                trustRepository,
+                featureFlags,
             )
         whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
         whenever(repository.primaryBouncerShow.value).thenReturn(false)
@@ -383,6 +393,55 @@
         verify(repository).setSideFpsShowing(false)
     }
 
+    @Test
+    fun delayBouncerWhenFaceAuthPossible() {
+        mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+        // GIVEN bouncer should be delayed due to face auth
+        featureFlags.apply { set(Flags.DELAY_BOUNCER, true) }
+        whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
+        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+            .thenReturn(true)
+
+        // WHEN bouncer show is requested
+        underTest.show(true)
+
+        // THEN primary show & primary showing soon aren't updated immediately
+        verify(repository, never()).setPrimaryShow(true)
+        verify(repository, never()).setPrimaryShowingSoon(false)
+
+        // WHEN all queued messages are dispatched
+        mainHandler.dispatchQueuedMessages()
+
+        // THEN primary show & primary showing soon are updated
+        verify(repository).setPrimaryShow(true)
+        verify(repository).setPrimaryShowingSoon(false)
+    }
+
+    @Test
+    fun delayBouncerWhenActiveUnlockPossible() {
+        mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+        // GIVEN bouncer should be delayed due to active unlock
+        featureFlags.apply { set(Flags.DELAY_BOUNCER, true) }
+        trustRepository.setCurrentUserActiveUnlockAvailable(true)
+        whenever(keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()).thenReturn(true)
+
+        // WHEN bouncer show is requested
+        underTest.show(true)
+
+        // THEN primary show & primary showing soon were scheduled to update
+        verify(repository, never()).setPrimaryShow(true)
+        verify(repository, never()).setPrimaryShowingSoon(false)
+
+        // WHEN all queued messages are dispatched
+        mainHandler.dispatchQueuedMessages()
+
+        // THEN primary show & primary showing soon are updated
+        verify(repository).setPrimaryShow(true)
+        verify(repository).setPrimaryShowingSoon(false)
+    }
+
     private fun updateSideFpsVisibilityParameters(
         isVisible: Boolean,
         sfpsEnabled: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index e35e971..5056b43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -25,9 +25,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.utils.os.FakeHandler
@@ -37,6 +39,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -71,7 +74,8 @@
                 dismissCallbackRegistry,
                 context,
                 keyguardUpdateMonitor,
-                keyguardBypassController,
+                Mockito.mock(TrustRepository::class.java),
+                FakeFeatureFlags(),
             )
     }
 
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 a5b78b74..3efe382 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/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index 0e9c99e..e9f1ac1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -24,9 +24,11 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
@@ -42,6 +44,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -77,7 +80,8 @@
                 dismissCallbackRegistry,
                 context,
                 keyguardUpdateMonitor,
-                keyguardBypassController,
+                Mockito.mock(TrustRepository::class.java),
+                FakeFeatureFlags(),
             )
         underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 4977775..f1bbd84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -132,7 +132,7 @@
     }
 
     @Test
-    fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesDisabled_bindsOnlyTasksWithHostProfile() {
+    fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesDisabled_bindsAllTasks() {
         givenEnterprisePoliciesFeatureFlag(enabled = false)
 
         val tasks =
@@ -147,11 +147,15 @@
 
         controller.init()
 
+        // TODO (b/263950746): Cross-profile filtering is removed for now. This should be brought
+        // back with the future fix
         verify(view)
             .bind(
                 listOf(
                     createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+                    createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
                     createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+                    createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
                     createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
                 )
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
index 26aa37d..d9db60d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
@@ -37,6 +37,8 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.retail.data.repository.FakeRetailModeRepository;
+import com.android.systemui.retail.domain.interactor.RetailModeInteractorImpl;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
@@ -67,6 +69,8 @@
     @Mock
     private ActivityStarter mActivityStarter;
 
+    private FakeRetailModeRepository mRetailModeRepository;
+
     private QSFooterViewController mController;
     private View mEditButton;
 
@@ -74,6 +78,9 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        mRetailModeRepository = new FakeRetailModeRepository();
+        mRetailModeRepository.setRetailMode(false);
+
         mEditButton = new View(mContext);
 
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
@@ -89,7 +96,8 @@
         when(mView.findViewById(android.R.id.edit)).thenReturn(mEditButton);
 
         mController = new QSFooterViewController(mView, mUserTracker, mFalsingManager,
-                mActivityStarter, mQSPanelController);
+                mActivityStarter, mQSPanelController,
+                new RetailModeInteractorImpl(mRetailModeRepository));
 
         mController.init();
     }
@@ -132,4 +140,20 @@
         captor.getValue().run();
         verify(mQSPanelController).showEdit(mEditButton);
     }
+
+    @Test
+    public void testEditButton_notRetailMode_visible() {
+        mRetailModeRepository.setRetailMode(false);
+
+        mController.setVisibility(View.VISIBLE);
+        assertThat(mEditButton.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testEditButton_retailMode_notVisible() {
+        mRetailModeRepository.setRetailMode(true);
+
+        mController.setVisibility(View.VISIBLE);
+        assertThat(mEditButton.getVisibility()).isEqualTo(View.GONE);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 50a8d26..fda63ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.retail.data.repository.FakeRetailModeRepository
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,6 +45,7 @@
 class TileSpecSettingsRepositoryTest : SysuiTestCase() {
 
     private lateinit var secureSettings: FakeSettings
+    private lateinit var retailModeRepository: FakeRetailModeRepository
 
     @Mock private lateinit var logger: QSPipelineLogger
 
@@ -57,9 +59,12 @@
         MockitoAnnotations.initMocks(this)
 
         secureSettings = FakeSettings()
+        retailModeRepository = FakeRetailModeRepository()
+        retailModeRepository.setRetailMode(false)
 
         with(context.orCreateTestableResources) {
             addOverride(R.string.quick_settings_tiles_default, DEFAULT_TILES)
+            addOverride(R.string.quick_settings_tiles_retail_mode, RETAIL_TILES)
         }
 
         underTest =
@@ -67,6 +72,7 @@
                 secureSettings,
                 context.resources,
                 logger,
+                retailModeRepository,
                 testDispatcher,
             )
     }
@@ -346,6 +352,26 @@
             assertThat(tiles).isEqualTo("b".toTileSpecs())
         }
 
+    @Test
+    fun retailMode_usesRetailTiles() =
+        testScope.runTest {
+            retailModeRepository.setRetailMode(true)
+
+            val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+            assertThat(tiles).isEqualTo(RETAIL_TILES.toTileSpecs())
+        }
+
+    @Test
+    fun retailMode_cannotModifyTiles() =
+        testScope.runTest {
+            retailModeRepository.setRetailMode(true)
+
+            underTest.setTiles(0, DEFAULT_TILES.toTileSpecs())
+
+            assertThat(loadTilesForUser(0)).isNull()
+        }
+
     private fun getDefaultTileSpecs(): List<TileSpec> {
         return QSHost.getDefaultSpecs(context.resources).map(TileSpec::create)
     }
@@ -360,6 +386,7 @@
 
     companion object {
         private const val DEFAULT_TILES = "a,b,c"
+        private const val RETAIL_TILES = "d"
         private const val SETTING = Settings.Secure.QS_TILES
 
         private fun String.toTileSpecs(): List<TileSpec> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
new file mode 100644
index 0000000..d7682b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.retail.data.repository
+
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RetailModeSettingsRepositoryTest : SysuiTestCase() {
+
+    private val globalSettings = FakeSettings()
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val underTest =
+        RetailModeSettingsRepository(
+            globalSettings,
+            backgroundDispatcher = testDispatcher,
+            scope = testScope.backgroundScope,
+        )
+
+    @Test
+    fun retailMode_defaultFalse() =
+        testScope.runTest {
+            val value by collectLastValue(underTest.retailMode)
+
+            assertThat(value).isFalse()
+            assertThat(underTest.inRetailMode).isFalse()
+        }
+
+    @Test
+    fun retailMode_false() =
+        testScope.runTest {
+            val value by collectLastValue(underTest.retailMode)
+
+            globalSettings.putInt(SETTING, 0)
+
+            assertThat(value).isFalse()
+            assertThat(underTest.inRetailMode).isFalse()
+        }
+
+    @Test
+    fun retailMode_true() =
+        testScope.runTest {
+            val value by collectLastValue(underTest.retailMode)
+
+            globalSettings.putInt(SETTING, 1)
+
+            assertThat(value).isTrue()
+            assertThat(underTest.inRetailMode).isTrue()
+        }
+
+    companion object {
+        private const val SETTING = Settings.Global.DEVICE_DEMO_MODE
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
new file mode 100644
index 0000000..8f13169
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.retail.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.retail.data.repository.FakeRetailModeRepository
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RetailModeInteractorImplTest : SysuiTestCase() {
+
+    private val retailModeRepository = FakeRetailModeRepository()
+
+    private val underTest = RetailModeInteractorImpl(retailModeRepository)
+
+    @Test
+    fun retailMode_false() {
+        retailModeRepository.setRetailMode(false)
+
+        assertThat(underTest.isInRetailMode).isFalse()
+    }
+
+    @Test
+    fun retailMode_true() {
+        retailModeRepository.setRetailMode(true)
+
+        assertThat(underTest.isInRetailMode).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
index 31a33d4..cbd9dba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
@@ -30,6 +30,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -127,6 +128,9 @@
 
     @Before
     public void setUp() {
+        assumeFalse("Skip test: does not apply to watches",
+            mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
+
         MockitoAnnotations.initMocks(this);
         mMainHandler = mContext.getMainThreadHandler();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
new file mode 100644
index 0000000..d4751c8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.shade
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableResources
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.fragments.FragmentService
+import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
[email protected]
+@SmallTest
+class NotificationsQSContainerControllerTest : SysuiTestCase() {
+
+    @Mock lateinit var view: NotificationsQuickSettingsContainer
+    @Mock lateinit var navigationModeController: NavigationModeController
+    @Mock lateinit var overviewProxyService: OverviewProxyService
+    @Mock lateinit var shadeHeaderController: ShadeHeaderController
+    @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+    @Mock lateinit var fragmentService: FragmentService
+
+    lateinit var underTest: NotificationsQSContainerController
+
+    private lateinit var fakeResources: TestableResources
+
+    private val delayableExecutor: DelayableExecutor = FakeExecutor(FakeSystemClock())
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        fakeResources = TestableResources(context.resources)
+
+        whenever(view.resources).thenReturn(fakeResources.resources)
+
+        underTest =
+            NotificationsQSContainerController(
+                view,
+                navigationModeController,
+                overviewProxyService,
+                shadeHeaderController,
+                shadeExpansionStateManager,
+                fragmentService,
+                delayableExecutor,
+            )
+    }
+
+    @Test
+    fun testSmallScreen_updateResources_splitShadeHeightIsSet() {
+        with(fakeResources) {
+            addOverride(R.bool.config_use_large_screen_shade_header, false)
+            addOverride(R.dimen.qs_header_height, 1)
+            addOverride(R.dimen.large_screen_shade_header_height, 2)
+        }
+
+        underTest.updateResources()
+
+        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+        verify(view).applyConstraints(capture(captor))
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(1)
+    }
+
+    @Test
+    fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
+        with(fakeResources) {
+            addOverride(R.bool.config_use_large_screen_shade_header, true)
+            addOverride(R.dimen.qs_header_height, 1)
+            addOverride(R.dimen.large_screen_shade_header_height, 2)
+        }
+
+        underTest.updateResources()
+
+        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+        verify(view).applyConstraints(capture(captor))
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2)
+    }
+}
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 9fe75ab..20da8a6 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/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index a1168f8..f0abf2f 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/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index d9d8b63..3b0d512 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -585,8 +585,6 @@
         // devices.
         layout.setBaselineAligned(false);
 
-        final boolean isRtl = mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
-
         // Add smart replies
         Button previous = null;
         SmartReplyView.SmartReplies smartReplies =
@@ -606,11 +604,7 @@
             if (previous != null) {
                 ViewGroup.MarginLayoutParams lp =
                         (ViewGroup.MarginLayoutParams) previous.getLayoutParams();
-                if (isRtl) {
-                    lp.leftMargin = mSpacing;
-                } else {
-                    lp.rightMargin = mSpacing;
-                }
+                lp.setMarginEnd(mSpacing);
             }
             layout.addView(current);
             previous = current;
@@ -634,11 +628,7 @@
             if (previous != null) {
                 ViewGroup.MarginLayoutParams lp =
                         (ViewGroup.MarginLayoutParams) previous.getLayoutParams();
-                if (isRtl) {
-                    lp.leftMargin = mSpacing;
-                } else {
-                    lp.rightMargin = mSpacing;
-                }
+                lp.setMarginEnd(mSpacing);
             }
             layout.addView(current);
             previous = current;
@@ -937,8 +927,8 @@
                 .collect(Collectors.toList());
         Button singleLineButton = buttons.get(0);
         Button doubleLineButton = buttons.get(1);
-        Drawable singleLineDrawable = singleLineButton.getCompoundDrawables()[0]; // left drawable
-        Drawable doubleLineDrawable = doubleLineButton.getCompoundDrawables()[0]; // left drawable
+        Drawable singleLineDrawable = singleLineButton.getCompoundDrawablesRelative()[0]; // start
+        Drawable doubleLineDrawable = doubleLineButton.getCompoundDrawablesRelative()[0]; // start
         assertEquals(singleLineDrawable.getBounds().width(),
                      doubleLineDrawable.getBounds().width());
         assertEquals(singleLineDrawable.getBounds().height(),
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 5b431e7..0983041 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,6 +14,8 @@
 
 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/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
index 6690de8..f0dbc60 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
@@ -19,13 +19,23 @@
 
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 
 class FakeTrustRepository : TrustRepository {
     private val _isCurrentUserTrusted = MutableStateFlow(false)
     override val isCurrentUserTrusted: Flow<Boolean>
         get() = _isCurrentUserTrusted
 
+    private val _isCurrentUserActiveUnlockAvailable = MutableStateFlow(false)
+    override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> =
+        _isCurrentUserActiveUnlockAvailable.asStateFlow()
+
     fun setCurrentUserTrusted(trust: Boolean) {
         _isCurrentUserTrusted.value = trust
     }
+
+    fun setCurrentUserActiveUnlockAvailable(available: Boolean) {
+        _isCurrentUserActiveUnlockAvailable.value = available
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/retail/data/repository/FakeRetailModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/retail/data/repository/FakeRetailModeRepository.kt
new file mode 100644
index 0000000..75b29ca
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/retail/data/repository/FakeRetailModeRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.retail.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeRetailModeRepository : RetailModeRepository {
+
+    private val _retailMode = MutableStateFlow(false)
+    override val retailMode: StateFlow<Boolean> = _retailMode.asStateFlow()
+
+    private var _retailModeValue = false
+    override val inRetailMode: Boolean
+        get() = _retailModeValue
+
+    fun setRetailMode(value: Boolean) {
+        _retailMode.value = value
+        _retailModeValue = value
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java b/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
index feae56e..b8bac61 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
@@ -157,6 +157,8 @@
                                         if (sDebug) {
                                             Log.d(TAG, "onSuccess Response: " + response);
                                         }
+                                        fieldClassificationServiceCallbacks
+                                                .onClassificationRequestSuccess(response);
                                     }
 
                                     @Override
@@ -165,6 +167,8 @@
                                         if (sDebug) {
                                             Log.d(TAG, "onFailure");
                                         }
+                                        fieldClassificationServiceCallbacks
+                                                .onClassificationRequestFailure(0, null);
                                     }
 
                                     @Override
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 3736262..9e46d73 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -204,6 +204,10 @@
         RemoteFieldClassificationService.FieldClassificationServiceCallbacks {
     private static final String TAG = "AutofillSession";
 
+    // This should never be true in production. This is only for local debugging.
+    // Otherwise it will spam logcat.
+    private static final boolean DBG = false;
+
     private static final String ACTION_DELAYED_FILL =
             "android.service.autofill.action.DELAYED_FILL";
     private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID";
@@ -1240,6 +1244,8 @@
 
     @GuardedBy("mLock")
     private void requestAssistStructureForPccLocked(int flags) {
+        if (!mClassificationState.shouldTriggerRequest()) return;
+        mClassificationState.updatePendingRequest();
         // Get request id
         int requestId;
         // TODO(b/158623971): Update this to prevent possible overflow
@@ -1571,12 +1577,18 @@
         // TODO(b/266379948): Ideally wait for PCC request to finish for a while more
         // (say 100ms) before proceeding further on.
 
+        if (DBG) {
+            Slog.d(TAG, "DBG: Initial response: " + response);
+        }
         synchronized (mLock) {
             response = getEffectiveFillResponse(response);
             if (isEmptyResponse(response)) {
                 // Treat it as a null response.
                 processNullResponseLocked(requestId, requestFlags);
             }
+            if (DBG) {
+                Slog.d(TAG, "DBG: Processed response: " + response);
+            }
             processResponseLocked(response, null, requestFlags);
         }
     }
@@ -1601,12 +1613,25 @@
         DatasetComputationContainer autofillProviderContainer = new DatasetComputationContainer();
         computeDatasetsForProviderAndUpdateContainer(response, autofillProviderContainer);
 
+        if (DBG) {
+            Slog.d(TAG, "DBG: computeDatasetsForProviderAndUpdateContainer: "
+                    + autofillProviderContainer);
+        }
         if (!mService.getMaster().isPccClassificationEnabled())  {
+            if (sVerbose) {
+                Slog.v(TAG, "PCC classification is disabled");
+            }
             return createShallowCopy(response, autofillProviderContainer);
         }
         synchronized (mLock) {
             if (mClassificationState.mState != ClassificationState.STATE_RESPONSE
                     || mClassificationState.mLastFieldClassificationResponse == null) {
+                if (sVerbose) {
+                    Slog.v(TAG, "PCC classification no last response:"
+                            + (mClassificationState.mLastFieldClassificationResponse == null)
+                            +   " ,ineligible state="
+                            + (mClassificationState.mState != ClassificationState.STATE_RESPONSE));
+                }
                 return createShallowCopy(response, autofillProviderContainer);
             }
             if (!mClassificationState.processResponse()) return response;
@@ -1614,11 +1639,22 @@
         boolean preferAutofillProvider = mService.getMaster().preferProviderOverPcc();
         boolean shouldUseFallback = mService.getMaster().shouldUsePccFallback();
         if (preferAutofillProvider && !shouldUseFallback) {
+            if (sVerbose) {
+                Slog.v(TAG, "preferAutofillProvider but no fallback");
+            }
             return createShallowCopy(response, autofillProviderContainer);
         }
 
+        if (DBG) {
+            synchronized (mLock) {
+                Slog.d(TAG, "DBG: ClassificationState: " + mClassificationState);
+            }
+        }
         DatasetComputationContainer detectionPccContainer = new DatasetComputationContainer();
         computeDatasetsForPccAndUpdateContainer(response, detectionPccContainer);
+        if (DBG) {
+            Slog.d(TAG, "DBG: computeDatasetsForPccAndUpdateContainer: " + detectionPccContainer);
+        }
 
         DatasetComputationContainer resultContainer;
         if (preferAutofillProvider) {
@@ -1692,6 +1728,20 @@
         // FillResponse.
         Set<Dataset> mDatasets = new LinkedHashSet<>();
         ArrayMap<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new ArrayMap<>();
+
+        public String toString() {
+            final StringBuilder builder = new StringBuilder("DatasetComputationContainer[");
+            if (mAutofillIds != null) {
+                builder.append(", autofillIds=").append(mAutofillIds);
+            }
+            if (mDatasets != null) {
+                builder.append(", mDatasets=").append(mDatasets);
+            }
+            if (mAutofillIdToDatasetMap != null) {
+                builder.append(", mAutofillIdToDatasetMap=").append(mAutofillIdToDatasetMap);
+            }
+            return builder.append(']').toString();
+        }
     }
 
     // Adds fallback datasets to the first container.
@@ -1843,7 +1893,6 @@
                 Dataset dataset = datasets.get(i);
                 if (dataset.getAutofillDatatypes() == null
                         || dataset.getAutofillDatatypes().isEmpty()) continue;
-                if (dataset.getFieldIds() != null && dataset.getFieldIds().size() > 0) continue;
 
                 ArrayList<AutofillId> fieldIds = new ArrayList<>();
                 ArrayList<AutofillValue> fieldValues = new ArrayList<>();
@@ -1852,9 +1901,10 @@
                 ArrayList<InlinePresentation> fieldInlinePresentations = new ArrayList<>();
                 ArrayList<InlinePresentation> fieldInlineTooltipPresentations = new ArrayList<>();
                 ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>();
+                Set<AutofillId> datasetAutofillIds = new ArraySet<>();
 
                 for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) {
-                    if (dataset.getAutofillDatatypes().get(0) == null) continue;
+                    if (dataset.getAutofillDatatypes().get(j) == null) continue;
                     String hint = dataset.getAutofillDatatypes().get(j);
 
                     if (hintsToAutofillIdMap.containsKey(hint)) {
@@ -1863,6 +1913,7 @@
 
                         for (AutofillId autofillId : tempIds) {
                             eligibleAutofillIds.add(autofillId);
+                            datasetAutofillIds.add(autofillId);
                             // For each of the field, copy over values.
                             fieldIds.add(autofillId);
                             fieldValues.add(dataset.getFieldValues().get(j));
@@ -1876,37 +1927,6 @@
                                     dataset.getFieldInlineTooltipPresentation(j));
                             fieldFilters.add(dataset.getFilter(j));
                         }
-
-                        Dataset newDataset =
-                                new Dataset(
-                                        fieldIds,
-                                        fieldValues,
-                                        fieldPresentations,
-                                        fieldDialogPresentations,
-                                        fieldInlinePresentations,
-                                        fieldInlineTooltipPresentations,
-                                        fieldFilters,
-                                        new ArrayList<>(),
-                                        dataset.getFieldContent(),
-                                        null,
-                                        null,
-                                        null,
-                                        null,
-                                        dataset.getId(),
-                                        dataset.getAuthentication());
-                        eligibleDatasets.add(newDataset);
-
-                        // Associate this dataset with all the ids that are represented with it.
-                        Set<Dataset> newDatasets;
-                        for (AutofillId autofillId : tempIds) {
-                            if (map.containsKey(autofillId)) {
-                                newDatasets = map.get(autofillId);
-                            } else {
-                                newDatasets = new ArraySet<>();
-                            }
-                            newDatasets.add(newDataset);
-                            map.put(autofillId, newDatasets);
-                        }
                     }
                     // TODO(b/266379948):  handle the case:
                     // groupHintsToAutofillIdMap.containsKey(hint))
@@ -1914,6 +1934,34 @@
                     // TODO(b/266379948):  also handle the case where there could be more types in
                     // the dataset, provided by the provider, however, they aren't applicable.
                 }
+                Dataset newDataset =
+                        new Dataset(
+                                fieldIds,
+                                fieldValues,
+                                fieldPresentations,
+                                fieldDialogPresentations,
+                                fieldInlinePresentations,
+                                fieldInlineTooltipPresentations,
+                                fieldFilters,
+                                new ArrayList<>(),
+                                dataset.getFieldContent(),
+                                null,
+                                null,
+                                null,
+                                null,
+                                dataset.getId(),
+                                dataset.getAuthentication());
+                eligibleDatasets.add(newDataset);
+                Set<Dataset> newDatasets;
+                for (AutofillId autofillId : datasetAutofillIds) {
+                    if (map.containsKey(autofillId)) {
+                        newDatasets = map.get(autofillId);
+                    } else {
+                        newDatasets = new ArraySet<>();
+                    }
+                    newDatasets.add(newDataset);
+                    map.put(autofillId, newDatasets);
+                }
             }
             container.mAutofillIds = eligibleAutofillIds;
             container.mDatasets = eligibleDatasets;
@@ -5319,6 +5367,26 @@
             mState = STATE_PENDING_REQUEST;
             mPendingFieldClassificationRequest = null;
         }
+
+        @GuardedBy("mLock")
+        private boolean shouldTriggerRequest() {
+            return mState == STATE_INITIAL || mState == STATE_INVALIDATED;
+        }
+
+        @GuardedBy("mLock")
+        @Override
+        public String toString() {
+            return "ClassificationState: ["
+                    + "state=" + stateToString()
+                    + ", mPendingFieldClassificationRequest=" + mPendingFieldClassificationRequest
+                    + ", mLastFieldClassificationResponse=" + mLastFieldClassificationResponse
+                    + ", mClassificationHintsMap=" + mClassificationHintsMap
+                    + ", mClassificationGroupHintsMap=" + mClassificationGroupHintsMap
+                    + ", mHintsToAutofillIdMap=" + mHintsToAutofillIdMap
+                    + ", mGroupHintsToAutofillIdMap=" + mGroupHintsToAutofillIdMap
+                    + "]";
+        }
+
     }
 
     @Override
@@ -5843,7 +5911,7 @@
         return serviceInfo == null ? Process.INVALID_UID : serviceInfo.applicationInfo.uid;
     }
 
-    // DetectionServiceCallbacks
+    // FieldClassificationServiceCallbacks
     public void onClassificationRequestSuccess(@Nullable FieldClassificationResponse response) {
         mClassificationState.updateResponseReceived(response);
     }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
index 6f99d86..e436e93 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
@@ -128,9 +128,9 @@
 
     private static final class CallMetadataSyncConnectionIdentifier {
         private final int mAssociationId;
-        private final long mCallId;
+        private final String mCallId;
 
-        CallMetadataSyncConnectionIdentifier(int associationId, long callId) {
+        CallMetadataSyncConnectionIdentifier(int associationId, String callId) {
             mAssociationId = associationId;
             mCallId = callId;
         }
@@ -139,7 +139,7 @@
             return mAssociationId;
         }
 
-        public long getCallId() {
+        public String getCallId() {
             return mCallId;
         }
 
@@ -161,9 +161,7 @@
 
     private abstract static class CallMetadataSyncConnectionCallback {
 
-        abstract void sendCallAction(int associationId, long callId, int action);
-
-        abstract void sendStateChange(int associationId, long callId, int newState);
+        abstract void sendCallAction(int associationId, String callId, int action);
     }
 
     private static class CallMetadataSyncConnection extends Connection {
@@ -184,7 +182,7 @@
             mCallback = callback;
         }
 
-        public long getCallId() {
+        public String getCallId() {
             return mCall.getId();
         }
 
@@ -205,22 +203,22 @@
             }
 
             final Bundle extras = new Bundle();
-            extras.putLong(CrossDeviceCall.EXTRA_CALL_ID, mCall.getId());
+            extras.putString(CrossDeviceCall.EXTRA_CALL_ID, mCall.getId());
             putExtras(extras);
 
             int capabilities = getConnectionCapabilities();
-            if (mCall.hasControl(android.companion.Telecom.Call.PUT_ON_HOLD)) {
+            if (mCall.hasControl(android.companion.Telecom.PUT_ON_HOLD)) {
                 capabilities |= CAPABILITY_HOLD;
             } else {
                 capabilities &= ~CAPABILITY_HOLD;
             }
-            if (mCall.hasControl(android.companion.Telecom.Call.MUTE)) {
+            if (mCall.hasControl(android.companion.Telecom.MUTE)) {
                 capabilities |= CAPABILITY_MUTE;
             } else {
                 capabilities &= ~CAPABILITY_MUTE;
             }
             mAudioManager.setMicrophoneMute(
-                    mCall.hasControl(android.companion.Telecom.Call.UNMUTE));
+                    mCall.hasControl(android.companion.Telecom.UNMUTE));
             if (capabilities != getConnectionCapabilities()) {
                 setConnectionCapabilities(capabilities);
             }
@@ -248,8 +246,8 @@
 
             int capabilities = getConnectionCapabilities();
             final boolean hasHoldControl = mCall.hasControl(
-                    android.companion.Telecom.Call.PUT_ON_HOLD)
-                    || mCall.hasControl(android.companion.Telecom.Call.TAKE_OFF_HOLD);
+                    android.companion.Telecom.PUT_ON_HOLD)
+                    || mCall.hasControl(android.companion.Telecom.TAKE_OFF_HOLD);
             if (hasHoldControl != ((getConnectionCapabilities() & CAPABILITY_HOLD)
                     == CAPABILITY_HOLD)) {
                 if (hasHoldControl) {
@@ -258,7 +256,7 @@
                     capabilities &= ~CAPABILITY_HOLD;
                 }
             }
-            final boolean hasMuteControl = mCall.hasControl(android.companion.Telecom.Call.MUTE);
+            final boolean hasMuteControl = mCall.hasControl(android.companion.Telecom.MUTE);
             if (hasMuteControl != ((getConnectionCapabilities() & CAPABILITY_MUTE)
                     == CAPABILITY_MUTE)) {
                 if (hasMuteControl) {
@@ -268,7 +266,7 @@
                 }
             }
             mAudioManager.setMicrophoneMute(
-                    mCall.hasControl(android.companion.Telecom.Call.UNMUTE));
+                    mCall.hasControl(android.companion.Telecom.UNMUTE));
             if (capabilities != getConnectionCapabilities()) {
                 setConnectionCapabilities(capabilities);
             }
@@ -276,12 +274,12 @@
 
         @Override
         public void onAnswer(int videoState) {
-            sendCallAction(android.companion.Telecom.Call.ACCEPT);
+            sendCallAction(android.companion.Telecom.ACCEPT);
         }
 
         @Override
         public void onReject() {
-            sendCallAction(android.companion.Telecom.Call.REJECT);
+            sendCallAction(android.companion.Telecom.REJECT);
         }
 
         @Override
@@ -296,33 +294,28 @@
 
         @Override
         public void onSilence() {
-            sendCallAction(android.companion.Telecom.Call.SILENCE);
+            sendCallAction(android.companion.Telecom.SILENCE);
         }
 
         @Override
         public void onHold() {
-            sendCallAction(android.companion.Telecom.Call.PUT_ON_HOLD);
+            sendCallAction(android.companion.Telecom.PUT_ON_HOLD);
         }
 
         @Override
         public void onUnhold() {
-            sendCallAction(android.companion.Telecom.Call.TAKE_OFF_HOLD);
+            sendCallAction(android.companion.Telecom.TAKE_OFF_HOLD);
         }
 
         @Override
         public void onMuteStateChanged(boolean isMuted) {
-            sendCallAction(isMuted ? android.companion.Telecom.Call.MUTE
-                    : android.companion.Telecom.Call.UNMUTE);
+            sendCallAction(isMuted ? android.companion.Telecom.MUTE
+                    : android.companion.Telecom.UNMUTE);
         }
 
         @Override
         public void onDisconnect() {
-            sendCallAction(android.companion.Telecom.Call.END);
-        }
-
-        @Override
-        public void onStateChanged(int state) {
-            mCallback.sendStateChange(mAssociationId, mCall.getId(), state);
+            sendCallAction(android.companion.Telecom.END);
         }
 
         private void sendCallAction(int action) {
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
index 1e4bb9a..5b0c745 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
@@ -33,14 +33,14 @@
 /** A read-only snapshot of an {@link ContextSyncMessage}. */
 class CallMetadataSyncData {
 
-    final Map<Long, CallMetadataSyncData.Call> mCalls = new HashMap<>();
+    final Map<String, CallMetadataSyncData.Call> mCalls = new HashMap<>();
     final List<CallMetadataSyncData.Call> mRequests = new ArrayList<>();
 
     public void addCall(CallMetadataSyncData.Call call) {
         mCalls.put(call.getId(), call);
     }
 
-    public boolean hasCall(long id) {
+    public boolean hasCall(String id) {
         return mCalls.containsKey(id);
     }
 
@@ -57,7 +57,7 @@
     }
 
     public static class Call implements Parcelable {
-        private long mId;
+        private String mId;
         private String mCallerId;
         private byte[] mAppIcon;
         private String mAppName;
@@ -67,7 +67,7 @@
 
         public static Call fromParcel(Parcel parcel) {
             final Call call = new Call();
-            call.setId(parcel.readLong());
+            call.setId(parcel.readString());
             call.setCallerId(parcel.readString());
             call.setAppIcon(parcel.readBlob());
             call.setAppName(parcel.readString());
@@ -82,7 +82,7 @@
 
         @Override
         public void writeToParcel(Parcel parcel, int parcelableFlags) {
-            parcel.writeLong(mId);
+            parcel.writeString(mId);
             parcel.writeString(mCallerId);
             parcel.writeBlob(mAppIcon);
             parcel.writeString(mAppName);
@@ -94,7 +94,7 @@
             }
         }
 
-        void setId(long id) {
+        void setId(String id) {
             mId = id;
         }
 
@@ -122,7 +122,7 @@
             mControls.add(control);
         }
 
-        long getId() {
+        String getId() {
             return mId;
         }
 
@@ -157,7 +157,7 @@
         @Override
         public boolean equals(Object other) {
             if (other instanceof CallMetadataSyncData.Call) {
-                return ((Call) other).getId() == getId();
+                return mId != null && mId.equals(((Call) other).getId());
             }
             return false;
         }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
index 443a732..0c23730 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
@@ -41,7 +41,6 @@
 public class CallMetadataSyncInCallService extends InCallService {
 
     private static final String TAG = "CallMetadataIcs";
-    private static final long NOT_VALID = -1L;
 
     private CompanionDeviceManagerServiceInternal mCdmsi;
 
@@ -71,7 +70,7 @@
                             callMetadataSyncData.getRequests().iterator();
                     while (iterator.hasNext()) {
                         final CallMetadataSyncData.Call call = iterator.next();
-                        if (call.getId() != 0) {
+                        if (call.getId() != null) {
                             // The call is already assigned an id; treat as control invocations.
                             for (int control : call.getControls()) {
                                 processCallControlAction(call.getId(), control);
@@ -81,41 +80,41 @@
                     }
                 }
 
-                private void processCallControlAction(long crossDeviceCallId,
+                private void processCallControlAction(String crossDeviceCallId,
                         int callControlAction) {
                     final CrossDeviceCall crossDeviceCall = getCallForId(crossDeviceCallId,
                             mCurrentCalls.values());
                     switch (callControlAction) {
-                        case android.companion.Telecom.Call.ACCEPT:
+                        case android.companion.Telecom.ACCEPT:
                             if (crossDeviceCall != null) {
                                 crossDeviceCall.doAccept();
                             }
                             break;
-                        case android.companion.Telecom.Call.REJECT:
+                        case android.companion.Telecom.REJECT:
                             if (crossDeviceCall != null) {
                                 crossDeviceCall.doReject();
                             }
                             break;
-                        case android.companion.Telecom.Call.SILENCE:
+                        case android.companion.Telecom.SILENCE:
                             doSilence();
                             break;
-                        case android.companion.Telecom.Call.MUTE:
+                        case android.companion.Telecom.MUTE:
                             doMute();
                             break;
-                        case android.companion.Telecom.Call.UNMUTE:
+                        case android.companion.Telecom.UNMUTE:
                             doUnmute();
                             break;
-                        case android.companion.Telecom.Call.END:
+                        case android.companion.Telecom.END:
                             if (crossDeviceCall != null) {
                                 crossDeviceCall.doEnd();
                             }
                             break;
-                        case android.companion.Telecom.Call.PUT_ON_HOLD:
+                        case android.companion.Telecom.PUT_ON_HOLD:
                             if (crossDeviceCall != null) {
                                 crossDeviceCall.doPutOnHold();
                             }
                             break;
-                        case android.companion.Telecom.Call.TAKE_OFF_HOLD:
+                        case android.companion.Telecom.TAKE_OFF_HOLD:
                             if (crossDeviceCall != null) {
                                 crossDeviceCall.doTakeOffHold();
                             }
@@ -171,12 +170,12 @@
 
     @Nullable
     @VisibleForTesting
-    CrossDeviceCall getCallForId(long crossDeviceCallId, Collection<CrossDeviceCall> calls) {
-        if (crossDeviceCallId == NOT_VALID) {
+    CrossDeviceCall getCallForId(String crossDeviceCallId, Collection<CrossDeviceCall> calls) {
+        if (crossDeviceCallId == null) {
             return null;
         }
         for (CrossDeviceCall crossDeviceCall : calls) {
-            if (crossDeviceCall.getId() == crossDeviceCallId) {
+            if (crossDeviceCallId.equals(crossDeviceCall.getId())) {
                 return crossDeviceCall;
             }
         }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
index ac981d4..168068e 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
@@ -23,7 +23,6 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.os.Bundle;
 import android.telecom.Call;
 import android.telecom.CallAudioState;
 import android.telecom.VideoProfile;
@@ -34,7 +33,7 @@
 import java.io.ByteArrayOutputStream;
 import java.util.HashSet;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
+import java.util.UUID;
 
 /** Data holder for a telecom call and additional metadata. */
 public class CrossDeviceCall {
@@ -45,9 +44,7 @@
             "com.android.companion.datatransfer.contextsync.extra.CALL_ID";
     private static final int APP_ICON_BITMAP_DIMENSION = 256;
 
-    private static final AtomicLong sNextId = new AtomicLong(1);
-
-    private final long mId;
+    private final String mId;
     private Call mCall;
     @VisibleForTesting boolean mIsEnterprise;
     @VisibleForTesting boolean mIsOtt;
@@ -64,14 +61,14 @@
             CallAudioState callAudioState) {
         this(packageManager, call.getDetails(), callAudioState);
         mCall = call;
-        final Bundle extras = new Bundle();
-        extras.putLong(EXTRA_CALL_ID, mId);
-        call.putExtras(extras);
+        call.putExtra(EXTRA_CALL_ID, mId);
     }
 
     CrossDeviceCall(PackageManager packageManager, Call.Details callDetails,
             CallAudioState callAudioState) {
-        mId = sNextId.getAndIncrement();
+        final String predefinedId = callDetails.getIntentExtras() != null
+                ? callDetails.getIntentExtras().getString(EXTRA_CALL_ID) : null;
+        mId = predefinedId != null ? predefinedId : UUID.randomUUID().toString();
         mCallingAppPackageName =
                 callDetails.getAccountHandle().getComponentName().getPackageName();
         mIsOtt = (callDetails.getCallCapabilities() & Call.Details.PROPERTY_SELF_MANAGED)
@@ -145,7 +142,7 @@
         if (mStatus == android.companion.Telecom.Call.RINGING) {
             mStatus = android.companion.Telecom.Call.RINGING_SILENCED;
         }
-        mControls.remove(android.companion.Telecom.Call.SILENCE);
+        mControls.remove(android.companion.Telecom.SILENCE);
     }
 
     @VisibleForTesting
@@ -156,26 +153,26 @@
         mControls.clear();
         if (mStatus == android.companion.Telecom.Call.RINGING
                 || mStatus == android.companion.Telecom.Call.RINGING_SILENCED) {
-            mControls.add(android.companion.Telecom.Call.ACCEPT);
-            mControls.add(android.companion.Telecom.Call.REJECT);
+            mControls.add(android.companion.Telecom.ACCEPT);
+            mControls.add(android.companion.Telecom.REJECT);
             if (mStatus == android.companion.Telecom.Call.RINGING) {
-                mControls.add(android.companion.Telecom.Call.SILENCE);
+                mControls.add(android.companion.Telecom.SILENCE);
             }
         }
         if (mStatus == android.companion.Telecom.Call.ONGOING
                 || mStatus == android.companion.Telecom.Call.ON_HOLD) {
-            mControls.add(android.companion.Telecom.Call.END);
+            mControls.add(android.companion.Telecom.END);
             if (callDetails.can(Call.Details.CAPABILITY_HOLD)) {
                 mControls.add(
                         mStatus == android.companion.Telecom.Call.ON_HOLD
-                                ? android.companion.Telecom.Call.TAKE_OFF_HOLD
-                                : android.companion.Telecom.Call.PUT_ON_HOLD);
+                                ? android.companion.Telecom.TAKE_OFF_HOLD
+                                : android.companion.Telecom.PUT_ON_HOLD);
             }
         }
         if (mStatus == android.companion.Telecom.Call.ONGOING && callDetails.can(
                 Call.Details.CAPABILITY_MUTE)) {
-            mControls.add(mIsMuted ? android.companion.Telecom.Call.UNMUTE
-                    : android.companion.Telecom.Call.MUTE);
+            mControls.add(mIsMuted ? android.companion.Telecom.UNMUTE
+                    : android.companion.Telecom.MUTE);
         }
     }
 
@@ -212,7 +209,7 @@
         }
     }
 
-    public long getId() {
+    public String getId() {
         return mId;
     }
 
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
index adc5faf..e5ab963 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
@@ -270,7 +270,7 @@
         while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
             switch (pis.getFieldNumber()) {
                 case (int) Telecom.Call.ID:
-                    call.setId(pis.readLong(Telecom.Call.ID));
+                    call.setId(pis.readString(Telecom.Call.ID));
                     break;
                 case (int) Telecom.Call.ORIGIN:
                     final long originToken = pis.start(Telecom.Call.ORIGIN);
@@ -336,7 +336,7 @@
     }
 
     /** Create a call control message. */
-    public static byte[] createCallControlMessage(long callId, int control) {
+    public static byte[] createCallControlMessage(String callId, int control) {
         final ProtoOutputStream pos = new ProtoOutputStream();
         pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION);
         final long telecomToken = pos.start(ContextSyncMessage.TELECOM);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1a5d425..a992765 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16060,6 +16060,8 @@
 
         final int procState = uidRec != null
                 ? uidRec.getSetProcState() : PROCESS_STATE_NONEXISTENT;
+        final int procAdj = uidRec != null
+                ? uidRec.getMinProcAdj() : ProcessList.INVALID_ADJ;
         final long procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
         final int capability = uidRec != null ? uidRec.getSetCapability() : 0;
         final boolean ephemeral = uidRec != null ? uidRec.isEphemeral() : isEphemeralLocked(uid);
@@ -16075,7 +16077,7 @@
         }
         final int enqueuedChange = mUidObserverController.enqueueUidChange(
                 uidRec == null ? null : uidRec.pendingChange,
-                uid, change, procState, procStateSeq, capability, ephemeral);
+                uid, change, procState, procAdj, procStateSeq, capability, ephemeral);
         if (uidRec != null) {
             uidRec.setLastReportedChange(enqueuedChange);
         }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 5a4d315..4a69f90 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -67,7 +67,6 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.IndentingPrintWriter;
@@ -78,6 +77,7 @@
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
+import com.android.server.pm.UserJourneyLogger;
 import com.android.server.pm.UserManagerInternal;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -1518,7 +1518,7 @@
             final UserInfo userInfo =
                     (umInternal != null) ? umInternal.getUserInfo(r.userId) : null;
             if (userInfo != null) {
-                userType = UserManager.getUserTypeForStatsd(userInfo.userType);
+                userType = UserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
             }
             Slog.i(TAG_BROADCAST,
                     "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 438a08c..0417b8c 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -644,6 +644,11 @@
         }
     }
 
+    @GuardedBy({"mService", "mProcLock"})
+    int getSetAdj() {
+        return mState.getSetAdj();
+    }
+
     @GuardedBy(anyOf = {"mService", "mProcLock"})
     IApplicationThread getThread() {
         return mThread;
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index 51cb987..790cc7b 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -96,7 +96,7 @@
     }
 
     int enqueueUidChange(@Nullable ChangeRecord currentRecord, int uid, int change, int procState,
-            long procStateSeq, int capability, boolean ephemeral) {
+            int procAdj, long procStateSeq, int capability, boolean ephemeral) {
         synchronized (mLock) {
             if (mPendingUidChanges.size() == 0) {
                 if (DEBUG_UID_OBSERVERS) {
@@ -117,6 +117,7 @@
             changeRecord.uid = uid;
             changeRecord.change = change;
             changeRecord.procState = procState;
+            changeRecord.procAdj = procAdj;
             changeRecord.procStateSeq = procStateSeq;
             changeRecord.capability = capability;
             changeRecord.ephemeral = ephemeral;
@@ -344,7 +345,7 @@
                     }
                     if ((reg.mWhich & ActivityManager.UID_OBSERVER_PROC_OOM_ADJ) != 0
                             && (change & UidRecord.CHANGE_PROCADJ) != 0) {
-                        observer.onUidProcAdjChanged(item.uid);
+                        observer.onUidProcAdjChanged(item.uid, item.procAdj);
                     }
                 }
                 final int duration = (int) (SystemClock.uptimeMillis() - start);
@@ -426,6 +427,7 @@
         public int uid;
         public int change;
         public int procState;
+        public int procAdj;
         public int capability;
         public boolean ephemeral;
         public long procStateSeq;
@@ -435,6 +437,7 @@
             changeRecord.uid = uid;
             changeRecord.change = change;
             changeRecord.procState = procState;
+            changeRecord.procAdj = procAdj;
             changeRecord.capability = capability;
             changeRecord.ephemeral = ephemeral;
             changeRecord.procStateSeq = procStateSeq;
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index e39ac2b..993088e 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -51,6 +51,12 @@
     private boolean mProcAdjChanged;
 
     @CompositeRWLock({"mService", "mProcLock"})
+    private int mCurAdj;
+
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mSetAdj;
+
+    @CompositeRWLock({"mService", "mProcLock"})
     private int mCurCapability;
 
     @CompositeRWLock({"mService", "mProcLock"})
@@ -201,12 +207,24 @@
         mProcAdjChanged = false;
     }
 
-    @GuardedBy({"mService", "mProcLock"})
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     boolean getProcAdjChanged() {
         return mProcAdjChanged;
     }
 
     @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getMinProcAdj() {
+        int minAdj = ProcessList.UNKNOWN_ADJ;
+        for (int i = mProcRecords.size() - 1; i >= 0; i--) {
+            int adj = mProcRecords.valueAt(i).getSetAdj();
+            if (adj < minAdj) {
+                minAdj = adj;
+            }
+        }
+        return minAdj;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     int getCurCapability() {
         return mCurCapability;
     }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b2fdee7..334c145 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -46,13 +46,24 @@
 import static com.android.server.am.UserState.STATE_RUNNING_LOCKED;
 import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED;
 import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKING;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_INVALID_SESSION_ID;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_BEGIN;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_FINISH;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_NONE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_START;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_STOP;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_SWITCH_FG;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOCKED_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOCKING_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED;
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
 import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE;
 import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND;
 import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
 import static com.android.server.pm.UserManagerInternal.userStartModeToString;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -123,6 +134,8 @@
 import com.android.server.SystemService.UserCompletedEventType;
 import com.android.server.SystemServiceManager;
 import com.android.server.am.UserState.KeyEvictedCallback;
+import com.android.server.pm.UserJourneyLogger;
+import com.android.server.pm.UserJourneyLogger.UserJourneySession;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
 import com.android.server.pm.UserManagerInternal.UserStartMode;
@@ -138,7 +151,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
@@ -221,75 +233,6 @@
     // TODO(b/197344658): Increase to 10s or 15s once we have a switch-UX-is-done invocation too.
     private static final int USER_COMPLETED_EVENT_DELAY_MS = 5 * 1000;
 
-    // Used for statsd logging with UserLifecycleJourneyReported + UserLifecycleEventOccurred atoms
-    private static final long INVALID_SESSION_ID = 0;
-
-    // The various user journeys, defined in the UserLifecycleJourneyReported atom for statsd
-    private static final int USER_JOURNEY_UNKNOWN =
-            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__UNKNOWN;
-    private static final int USER_JOURNEY_USER_SWITCH_FG =
-            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_FG;
-    private static final int USER_JOURNEY_USER_SWITCH_UI =
-            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_UI;
-    private static final int USER_JOURNEY_USER_START =
-            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_START;
-    private static final int USER_JOURNEY_USER_CREATE =
-            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE;
-    private static final int USER_JOURNEY_USER_STOP =
-            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_STOP;
-    @IntDef(prefix = { "USER_JOURNEY" }, value = {
-            USER_JOURNEY_UNKNOWN,
-            USER_JOURNEY_USER_SWITCH_FG,
-            USER_JOURNEY_USER_SWITCH_UI,
-            USER_JOURNEY_USER_START,
-            USER_JOURNEY_USER_CREATE,
-            USER_JOURNEY_USER_STOP
-    })
-    @interface UserJourney {}
-
-    // The various user lifecycle events, defined in the UserLifecycleEventOccurred atom for statsd
-    private static final int USER_LIFECYCLE_EVENT_UNKNOWN =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN;
-    private static final int USER_LIFECYCLE_EVENT_SWITCH_USER =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__SWITCH_USER;
-    private static final int USER_LIFECYCLE_EVENT_START_USER =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__START_USER;
-    private static final int USER_LIFECYCLE_EVENT_CREATE_USER =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
-    private static final int USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__USER_RUNNING_LOCKED;
-    private static final int USER_LIFECYCLE_EVENT_UNLOCKING_USER =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKING_USER;
-    private static final int USER_LIFECYCLE_EVENT_UNLOCKED_USER =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKED_USER;
-    private static final int USER_LIFECYCLE_EVENT_STOP_USER =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__STOP_USER;
-    @IntDef(prefix = { "USER_LIFECYCLE_EVENT" }, value = {
-            USER_LIFECYCLE_EVENT_UNKNOWN,
-            USER_LIFECYCLE_EVENT_SWITCH_USER,
-            USER_LIFECYCLE_EVENT_START_USER,
-            USER_LIFECYCLE_EVENT_CREATE_USER,
-            USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
-            USER_LIFECYCLE_EVENT_UNLOCKING_USER,
-            USER_LIFECYCLE_EVENT_UNLOCKED_USER,
-            USER_LIFECYCLE_EVENT_STOP_USER
-    })
-    @interface UserLifecycleEvent {}
-
-    // User lifecyle event state, defined in the UserLifecycleEventOccurred atom for statsd
-    private static final int USER_LIFECYCLE_EVENT_STATE_BEGIN =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN;
-    private static final int USER_LIFECYCLE_EVENT_STATE_FINISH =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH;
-    private static final int USER_LIFECYCLE_EVENT_STATE_NONE =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE;
-    @IntDef(prefix = { "USER_LIFECYCLE_EVENT_STATE" }, value = {
-            USER_LIFECYCLE_EVENT_STATE_BEGIN,
-            USER_LIFECYCLE_EVENT_STATE_FINISH,
-            USER_LIFECYCLE_EVENT_STATE_NONE,
-    })
-    @interface UserLifecycleEventState {}
-
     /**
      * Maximum number of users we allow to be running at a time, including system user.
      *
@@ -420,13 +363,6 @@
     private final ArrayList<Integer> mLastActiveUsers = new ArrayList<>();
 
     /**
-     * {@link UserIdInt} to {@link UserJourneySession} mapping used for statsd logging for the
-     * UserLifecycleJourneyReported and UserLifecycleEventOccurred atoms.
-     */
-    @GuardedBy("mUserIdToUserJourneyMap")
-    private final SparseArray<UserJourneySession> mUserIdToUserJourneyMap = new SparseArray<>();
-
-    /**
      * Map of userId to {@link UserCompletedEventType} event flags, indicating which as-yet-
      * unreported user-starting events have transpired for the given user.
      */
@@ -621,8 +557,9 @@
         // but we might immediately step into RUNNING below if the user
         // storage is already unlocked.
         if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
-            logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
-                    USER_LIFECYCLE_EVENT_STATE_NONE);
+            mInjector.getUserJourneyLogger()
+                    .logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
+                    EVENT_STATE_NONE);
             mInjector.getUserManagerInternal().setUserState(userId, uss.state);
             // Do not report secondary users, runtime restarts or first boot/upgrade
             if (userId == UserHandle.USER_SYSTEM
@@ -646,7 +583,10 @@
                 // user transitions to RUNNING_LOCKED.  However, in "headless system user mode", the
                 // system user is explicitly started before the device has finished booting.  In
                 // that case, we need to wait until onBootComplete() to send the broadcast.
-                if (!(mInjector.isHeadlessSystemUserMode() && uss.mHandle.isSystem())) {
+                // Similarly, this occurs after a user switch, but in HSUM we switch to the main
+                // user before boot is complete, so again this should be delayed until
+                // onBootComplete if boot has not yet completed.
+                if (mAllowUserUnlocking) {
                     // ACTION_LOCKED_BOOT_COMPLETED
                     sendLockedBootCompletedBroadcast(resultTo, userId);
                 }
@@ -694,8 +634,9 @@
     private boolean finishUserUnlocking(final UserState uss) {
         final int userId = uss.mHandle.getIdentifier();
         EventLog.writeEvent(EventLogTags.UC_FINISH_USER_UNLOCKING, userId);
-        logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
-                USER_LIFECYCLE_EVENT_STATE_BEGIN);
+        mInjector.getUserJourneyLogger()
+                .logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
+                EVENT_STATE_BEGIN);
         // If the user key hasn't been unlocked yet, we cannot proceed.
         if (!StorageManager.isUserKeyUnlocked(userId)) return false;
         synchronized (mLock) {
@@ -1073,9 +1014,7 @@
             return;
         }
 
-        logUserJourneyInfo(null, getUserInfo(userId), USER_JOURNEY_USER_STOP);
-        logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
-                USER_LIFECYCLE_EVENT_STATE_BEGIN);
+        logUserJourneyBegin(userId, USER_JOURNEY_USER_STOP);
 
         if (stopUserCallback != null) {
             uss.mStopCallbacks.add(stopUserCallback);
@@ -1138,9 +1077,16 @@
         synchronized (mLock) {
             if (uss.state != UserState.STATE_STOPPING) {
                 // Whoops, we are being started back up.  Abort, abort!
-                logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
-                        USER_LIFECYCLE_EVENT_STATE_NONE);
-                clearSessionId(userId);
+                UserJourneySession session = mInjector.getUserJourneyLogger()
+                        .logUserJourneyFinishWithError(-1, getUserInfo(userId),
+                                USER_JOURNEY_USER_STOP, ERROR_CODE_ABORTED);
+                if (session != null) {
+                    mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
+                } else {
+                    mInjector.getUserJourneyLogger()
+                            .logUserJourneyFinishWithError(-1, getUserInfo(userId),
+                                    USER_JOURNEY_USER_STOP, ERROR_CODE_INVALID_SESSION_ID);
+                }
                 return;
             }
             uss.setState(UserState.STATE_SHUTDOWN);
@@ -1247,9 +1193,11 @@
                 mInjector.getUserManager().removeUserEvenWhenDisallowed(userId);
             }
 
-            logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
-                    USER_LIFECYCLE_EVENT_STATE_FINISH);
-            clearSessionId(userId);
+            UserJourneySession session = mInjector.getUserJourneyLogger()
+                    .logUserJourneyFinish(-1, userInfo, USER_JOURNEY_USER_STOP);
+            if (session != null) {
+                mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
+            }
 
             if (lockUser) {
                 dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
@@ -1259,9 +1207,11 @@
             // which was paused while the SHUTDOWN flow of the user was in progress.
             resumePendingUserStarts(userId);
         } else {
-            logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
-                    USER_LIFECYCLE_EVENT_STATE_NONE);
-            clearSessionId(userId);
+            UserJourneySession session = mInjector.getUserJourneyLogger()
+                    .finishAndClearIncompleteUserJourney(userId, USER_JOURNEY_USER_STOP);
+            if (session != null) {
+                mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
+            }
         }
     }
 
@@ -2617,9 +2567,9 @@
         // we should *not* transition users out of the BOOTING state using finishUserBoot(), as that
         // doesn't handle issuing the needed onUserStarting() call, and it would just race with an
         // explicit start anyway.  We do, however, need to send the "locked boot complete" broadcast
-        // for the system user, as that got skipped earlier due to the *device* boot not being
-        // complete yet.  We also need to try to unlock all started users, since until now explicit
-        // user starts didn't proceed to unlocking, due to it being too early in the device boot.
+        // as that got skipped earlier due to the *device* boot not being complete yet.
+        // We also need to try to unlock all started users, since until now explicit user starts
+        // didn't proceed to unlocking, due to it being too early in the device boot.
         //
         // USER_SYSTEM must be processed first.  It will be first in the array, as its ID is lowest.
         Preconditions.checkArgument(startedUsers.keyAt(0) == UserHandle.USER_SYSTEM);
@@ -2629,9 +2579,7 @@
             if (!mInjector.isHeadlessSystemUserMode()) {
                 finishUserBoot(uss, resultTo);
             } else {
-                if (userId == UserHandle.USER_SYSTEM) {
-                    sendLockedBootCompletedBroadcast(resultTo, userId);
-                }
+                sendLockedBootCompletedBroadcast(resultTo, userId);
                 maybeUnlockUser(userId);
             }
         }
@@ -3135,10 +3083,7 @@
     public boolean handleMessage(Message msg) {
         switch (msg.what) {
             case START_USER_SWITCH_FG_MSG:
-                logUserJourneyInfo(getUserInfo(getCurrentUserId()), getUserInfo(msg.arg1),
-                        USER_JOURNEY_USER_SWITCH_FG);
-                logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_SWITCH_USER,
-                        USER_LIFECYCLE_EVENT_STATE_BEGIN);
+                logUserJourneyBegin(msg.arg1, USER_JOURNEY_USER_SWITCH_FG);
                 startUserInForeground(msg.arg1);
                 break;
             case REPORT_USER_SWITCH_MSG:
@@ -3160,18 +3105,15 @@
                 mInjector.batteryStatsServiceNoteEvent(
                         BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
                         Integer.toString(msg.arg1), msg.arg1);
-                logUserJourneyInfo(null, getUserInfo(msg.arg1), USER_JOURNEY_USER_START);
-                logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
-                        USER_LIFECYCLE_EVENT_STATE_BEGIN);
+                logUserJourneyBegin(msg.arg1, USER_JOURNEY_USER_START);
 
                 mInjector.onUserStarting(/* userId= */ msg.arg1);
                 scheduleOnUserCompletedEvent(msg.arg1,
                         UserCompletedEventType.EVENT_TYPE_USER_STARTING,
                         USER_COMPLETED_EVENT_DELAY_MS);
 
-                logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
-                        USER_LIFECYCLE_EVENT_STATE_FINISH);
-                clearSessionId(msg.arg1, USER_JOURNEY_USER_START);
+                mInjector.getUserJourneyLogger().logUserJourneyFinish(-1 , getUserInfo(msg.arg1),
+                        USER_JOURNEY_USER_START);
                 break;
             case USER_UNLOCK_MSG:
                 final int userId = msg.arg1;
@@ -3180,10 +3122,11 @@
                 FgThread.getHandler().post(() -> {
                     mInjector.loadUserRecents(userId);
                 });
-                logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
-                        USER_LIFECYCLE_EVENT_STATE_FINISH);
-                logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKED_USER,
-                        USER_LIFECYCLE_EVENT_STATE_BEGIN);
+
+                mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
+                        USER_LIFECYCLE_EVENT_UNLOCKING_USER, EVENT_STATE_FINISH);
+                mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
+                        USER_LIFECYCLE_EVENT_UNLOCKED_USER, EVENT_STATE_BEGIN);
 
                 final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
                 t.traceBegin("finishUserUnlocked-" + userId);
@@ -3199,9 +3142,9 @@
                         // (No need to acquire lock to read mCurrentUserId since it is volatile.)
                         // TODO: Find something to wait for in the case of a profile.
                         mCurrentUserId == msg.arg1 ? USER_COMPLETED_EVENT_DELAY_MS : 1000);
-                logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKED_USER,
-                        USER_LIFECYCLE_EVENT_STATE_FINISH);
-                clearSessionId(msg.arg1);
+                mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
+                        USER_LIFECYCLE_EVENT_UNLOCKED_USER, EVENT_STATE_FINISH);
+                // Unlocking user is not a journey no need to clear sessionId
                 break;
             case USER_CURRENT_MSG:
                 mInjector.batteryStatsServiceNoteEvent(
@@ -3224,22 +3167,24 @@
                 break;
             case REPORT_USER_SWITCH_COMPLETE_MSG:
                 dispatchUserSwitchComplete(msg.arg1, msg.arg2);
-                logUserLifecycleEvent(msg.arg2, USER_LIFECYCLE_EVENT_SWITCH_USER,
-                        USER_LIFECYCLE_EVENT_STATE_FINISH);
+                UserJourneySession session = mInjector.getUserJourneyLogger()
+                        .logUserSwitchJourneyFinish(msg.arg1, getUserInfo(msg.arg2));
+                if (session != null) {
+                    mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
+                }
                 break;
             case REPORT_LOCKED_BOOT_COMPLETE_MSG:
                 dispatchLockedBootComplete(msg.arg1);
                 break;
             case START_USER_SWITCH_UI_MSG:
                 final Pair<UserInfo, UserInfo> fromToUserPair = (Pair<UserInfo, UserInfo>) msg.obj;
-                logUserJourneyInfo(fromToUserPair.first, fromToUserPair.second,
-                        USER_JOURNEY_USER_SWITCH_UI);
-                logUserLifecycleEvent(fromToUserPair.second.id, USER_LIFECYCLE_EVENT_SWITCH_USER,
-                        USER_LIFECYCLE_EVENT_STATE_BEGIN);
+                logUserJourneyBegin(fromToUserPair.second.id, USER_JOURNEY_USER_SWITCH_UI);
                 showUserSwitchDialog(fromToUserPair);
                 break;
             case CLEAR_USER_JOURNEY_SESSION_MSG:
-                logAndClearSessionId(msg.arg1);
+                mInjector.getUserJourneyLogger()
+                        .finishAndClearIncompleteUserJourney(msg.arg1, msg.arg2);
+                mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, msg.obj);
                 break;
             case COMPLETE_USER_SWITCH_MSG:
                 completeUserSwitch(msg.arg1, msg.arg2);
@@ -3317,123 +3262,29 @@
      * statsd helper method for logging the start of a user journey via a UserLifecycleEventOccurred
      * atom given the originating and targeting users for the journey.
      */
-    private void logUserJourneyInfo(UserInfo origin, UserInfo target, @UserJourney int journey) {
-        final long newSessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
-        synchronized (mUserIdToUserJourneyMap) {
-            UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(target.id);
-            if (userJourneySession != null) {
-                // TODO(b/157007231): Move this logic to a separate class/file.
-                if ((userJourneySession.mJourney == USER_JOURNEY_USER_SWITCH_UI
-                        || userJourneySession.mJourney == USER_JOURNEY_USER_SWITCH_FG)
-                        && (journey == USER_JOURNEY_USER_START
-                                || journey == USER_JOURNEY_USER_STOP)) {
-                    /*
-                     * There is already a user switch journey, and a user start or stop journey for
-                     * the same target user received. New journey is most likely a part of user
-                     * switch journey so no need to create a new journey.
-                     */
-                    if (DEBUG_MU) {
-                        Slogf.d(TAG, journey + " not logged as it is expected to be part of "
-                                + userJourneySession.mJourney);
-                    }
-                    return;
-                }
-                /*
-                 * Possible reasons for this condition to be true:
-                 * - A user switch journey is received while another user switch journey is in
-                 *   process for the same user.
-                 * - A user switch journey is received while user start journey is in process for
-                 *   the same user.
-                 * - A user start journey is received while another user start journey is in process
-                 *   for the same user.
-                 * In all cases potentially an incomplete, timed-out session or multiple
-                 * simultaneous requests. It is not possible to keep track of multiple sessions for
-                 * the same user, so previous session is abandoned.
-                 */
-                FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED,
-                        userJourneySession.mSessionId, target.id, USER_LIFECYCLE_EVENT_UNKNOWN,
-                        USER_LIFECYCLE_EVENT_STATE_NONE);
-            }
-
+    private void logUserJourneyBegin(int targetId,
+            @UserJourneyLogger.UserJourney int journey) {
+        UserJourneySession oldSession = mInjector.getUserJourneyLogger()
+                    .finishAndClearIncompleteUserJourney(targetId, journey);
+        if (oldSession != null) {
             if (DEBUG_MU) {
                 Slogf.d(TAG,
-                        "Starting a new journey: " + journey + " with session id: " + newSessionId);
+                        "Starting a new journey: " + journey + " with session id: "
+                                + oldSession);
             }
-
-            userJourneySession = new UserJourneySession(newSessionId, journey);
-            mUserIdToUserJourneyMap.put(target.id, userJourneySession);
             /*
-             * User lifecyle journey would be complete when {@code #clearSessionId} is called after
-             * the last expected lifecycle event for the journey. It may be possible that the last
-             * event is not called, e.g., user not unlocked after user switching. In such cases user
-             * journey is cleared after {@link USER_JOURNEY_TIMEOUT}.
+             * User lifecycle journey would be complete when {@code #clearSessionId} is called
+             * after the last expected lifecycle event for the journey. It may be possible that
+             * the last event is not called, e.g., user not unlocked after user switching. In such
+             * cases user journey is cleared after {@link USER_JOURNEY_TIMEOUT}.
              */
-            mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG);
-            mHandler.sendMessageDelayed(mHandler.obtainMessage(CLEAR_USER_JOURNEY_SESSION_MSG,
-                    target.id, /* arg2= */ 0), USER_JOURNEY_TIMEOUT_MS);
+            mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, oldSession);
         }
+        UserJourneySession newSession = mInjector.getUserJourneyLogger()
+                .logUserJourneyBegin(targetId, journey);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(CLEAR_USER_JOURNEY_SESSION_MSG,
+                targetId, /* arg2= */ journey, newSession), USER_JOURNEY_TIMEOUT_MS);
 
-        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, newSessionId,
-                journey, origin != null ? origin.id : -1,
-                target.id, UserManager.getUserTypeForStatsd(target.userType), target.flags);
-    }
-
-    /**
-     * statsd helper method for logging the given event for the UserLifecycleEventOccurred statsd
-     * atom.
-     */
-    private void logUserLifecycleEvent(@UserIdInt int userId, @UserLifecycleEvent int event,
-            @UserLifecycleEventState int eventState) {
-        final long sessionId;
-        synchronized (mUserIdToUserJourneyMap) {
-            final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId);
-            if (userJourneySession == null || userJourneySession.mSessionId == INVALID_SESSION_ID) {
-                Slogf.w(TAG, "UserLifecycleEvent " + event
-                        + " received without an active userJourneySession.");
-                return;
-            }
-            sessionId = userJourneySession.mSessionId;
-        }
-
-        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
-                event, eventState);
-    }
-
-    /**
-     * Clears the {@link UserJourneySession} for a given {@link UserIdInt} and {@link UserJourney}.
-     */
-    private void clearSessionId(@UserIdInt int userId, @UserJourney int journey) {
-        synchronized (mUserIdToUserJourneyMap) {
-            final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId);
-            if (userJourneySession != null && userJourneySession.mJourney == journey) {
-                clearSessionId(userId);
-            }
-        }
-    }
-
-    /**
-     * Clears the {@link UserJourneySession} for a given {@link UserIdInt}.
-     */
-    private void clearSessionId(@UserIdInt int userId) {
-        synchronized (mUserIdToUserJourneyMap) {
-            mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG);
-            mUserIdToUserJourneyMap.delete(userId);
-        }
-    }
-
-    /**
-     * Log a final event of the {@link UserJourneySession} and clear it.
-     */
-    private void logAndClearSessionId(@UserIdInt int userId) {
-        synchronized (mUserIdToUserJourneyMap) {
-            final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId);
-            if (userJourneySession != null) {
-                FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED,
-                        userJourneySession.mSessionId, userId, USER_LIFECYCLE_EVENT_UNKNOWN,
-                        USER_LIFECYCLE_EVENT_STATE_NONE);
-            }
-            clearSessionId(userId);
-        }
     }
 
     private BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
@@ -3471,23 +3322,6 @@
         return mLastUserUnlockingUptime;
     }
 
-    /**
-     * Helper class to store user journey and session id.
-     *
-     * <p> User journey tracks a chain of user lifecycle events occurring during different user
-     * activities such as user start, user switch, and user creation.
-     */
-    // TODO(b/157007231): Move this class and user journey tracking logic to a separate file.
-    private static class UserJourneySession {
-        final long mSessionId;
-        @UserJourney final int mJourney;
-
-        UserJourneySession(long sessionId, @UserJourney int journey) {
-            mJourney = journey;
-            mSessionId = sessionId;
-        }
-    }
-
     private static class UserProgressListener extends IProgressListener.Stub {
         private volatile long mUnlockStarted;
         @Override
@@ -3562,6 +3396,10 @@
             return new Handler(mService.mUiHandler.getLooper(), callback);
         }
 
+        protected UserJourneyLogger getUserJourneyLogger() {
+            return getUserManager().getUserJourneyLogger();
+        }
+
         protected Context getContext() {
             return mService.mContext;
         }
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 412fbe79..4e7865c 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -33,6 +33,8 @@
 import android.graphics.drawable.Animatable2;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -53,6 +55,8 @@
 import com.android.internal.util.UserIcons;
 import com.android.server.wm.WindowManagerService;
 
+import java.util.concurrent.atomic.AtomicBoolean;
+
 /**
  * Dialog to show during the user switch. This dialog shows target user's name and their profile
  * picture with a circular spinner animation around it if the animations for this dialog are not
@@ -64,9 +68,14 @@
 
     // User switching doesn't happen that frequently, so it doesn't hurt to have it always on
     protected static final boolean DEBUG = true;
+
     private static final long DIALOG_SHOW_HIDE_ANIMATION_DURATION_MS = 300;
     private final boolean mDisableAnimations;
 
+    // Time to wait for the onAnimationEnd() callbacks before moving on
+    private static final int ANIMATION_TIMEOUT_MS = 1000;
+    private final Handler mHandler = new Handler(Looper.myLooper());
+
     protected final UserInfo mOldUser;
     protected final UserInfo mNewUser;
     private final String mSwitchingFromSystemUserMessage;
@@ -180,7 +189,7 @@
 
     @Override
     public void show() {
-        asyncTraceBegin("", 0);
+        asyncTraceBegin("dialog", 0);
         super.show();
     }
 
@@ -188,7 +197,7 @@
     public void dismiss() {
         super.dismiss();
         stopFreezingScreen();
-        asyncTraceEnd("", 0);
+        asyncTraceEnd("dialog", 0);
     }
 
     public void show(@NonNull Runnable onShown) {
@@ -217,20 +226,18 @@
         if (!mNeedToFreezeScreen) {
             return;
         }
-        if (DEBUG) Slog.d(TAG, "startFreezingScreen");
-        Trace.traceBegin(TRACE_TAG, "startFreezingScreen");
+        traceBegin("startFreezingScreen");
         mWindowManager.startFreezingScreen(0, 0);
-        Trace.traceEnd(TRACE_TAG);
+        traceEnd("startFreezingScreen");
     }
 
     private void stopFreezingScreen() {
         if (!mNeedToFreezeScreen) {
             return;
         }
-        if (DEBUG) Slog.d(TAG, "stopFreezingScreen");
-        Trace.traceBegin(TRACE_TAG, "stopFreezingScreen");
+        traceBegin("stopFreezingScreen");
         mWindowManager.stopFreezingScreen();
-        Trace.traceEnd(TRACE_TAG);
+        traceEnd("stopFreezingScreen");
     }
 
     private void startShowAnimation(Runnable onAnimationEnd) {
@@ -238,13 +245,13 @@
             onAnimationEnd.run();
             return;
         }
-        asyncTraceBegin("-showAnimation", 1);
-        startDialogAnimation(new AlphaAnimation(0, 1), () -> {
-            asyncTraceEnd("-showAnimation", 1);
+        asyncTraceBegin("showAnimation", 1);
+        startDialogAnimation("show", new AlphaAnimation(0, 1), () -> {
+            asyncTraceEnd("showAnimation", 1);
 
-            asyncTraceBegin("-spinnerAnimation", 2);
+            asyncTraceBegin("spinnerAnimation", 2);
             startProgressAnimation(() -> {
-                asyncTraceEnd("-spinnerAnimation", 2);
+                asyncTraceEnd("spinnerAnimation", 2);
 
                 onAnimationEnd.run();
             });
@@ -257,9 +264,9 @@
             onAnimationEnd.run();
             return;
         }
-        asyncTraceBegin("-dismissAnimation", 3);
-        startDialogAnimation(new AlphaAnimation(1, 0), () -> {
-            asyncTraceEnd("-dismissAnimation", 3);
+        asyncTraceBegin("dismissAnimation", 3);
+        startDialogAnimation("dismiss", new AlphaAnimation(1, 0), () -> {
+            asyncTraceEnd("dismissAnimation", 3);
 
             onAnimationEnd.run();
         });
@@ -271,10 +278,11 @@
             onAnimationEnd.run();
             return;
         }
+        final Runnable onAnimationEndWithTimeout = animationWithTimeout("spinner", onAnimationEnd);
         avd.registerAnimationCallback(new Animatable2.AnimationCallback() {
             @Override
             public void onAnimationEnd(Drawable drawable) {
-                onAnimationEnd.run();
+                onAnimationEndWithTimeout.run();
             }
         });
         avd.start();
@@ -291,12 +299,13 @@
         return null;
     }
 
-    private void startDialogAnimation(Animation animation, Runnable onAnimationEnd) {
+    private void startDialogAnimation(String name, Animation animation, Runnable onAnimationEnd) {
         final View view = findViewById(R.id.content);
         if (mDisableAnimations || view == null) {
             onAnimationEnd.run();
             return;
         }
+        final Runnable onAnimationEndWithTimeout = animationWithTimeout(name, onAnimationEnd);
         animation.setDuration(DIALOG_SHOW_HIDE_ANIMATION_DURATION_MS);
         animation.setAnimationListener(new Animation.AnimationListener() {
             @Override
@@ -306,7 +315,7 @@
 
             @Override
             public void onAnimationEnd(Animation animation) {
-                onAnimationEnd.run();
+                onAnimationEndWithTimeout.run();
             }
 
             @Override
@@ -317,11 +326,39 @@
         view.startAnimation(animation);
     }
 
+    private Runnable animationWithTimeout(String name, Runnable onAnimationEnd) {
+        final AtomicBoolean isFirst = new AtomicBoolean(true);
+        final Runnable onAnimationEndOrTimeout = () -> {
+            if (isFirst.getAndSet(false)) {
+                mHandler.removeCallbacksAndMessages(null);
+                onAnimationEnd.run();
+            }
+        };
+        mHandler.postDelayed(() -> {
+            Slog.w(TAG, name + " animation not completed in " + ANIMATION_TIMEOUT_MS + " ms");
+            onAnimationEndOrTimeout.run();
+        }, ANIMATION_TIMEOUT_MS);
+
+        return onAnimationEndOrTimeout;
+    }
+
     private void asyncTraceBegin(String subTag, int subCookie) {
+        if (DEBUG) Slog.d(TAG, "asyncTraceBegin-" + subTag);
         Trace.asyncTraceBegin(TRACE_TAG, TAG + subTag, mTraceCookie + subCookie);
     }
 
     private void asyncTraceEnd(String subTag, int subCookie) {
         Trace.asyncTraceEnd(TRACE_TAG, TAG + subTag, mTraceCookie + subCookie);
+        if (DEBUG) Slog.d(TAG, "asyncTraceEnd-" + subTag);
+    }
+
+    private void traceBegin(String msg) {
+        if (DEBUG) Slog.d(TAG, "traceBegin-" + msg);
+        Trace.traceBegin(TRACE_TAG, msg);
+    }
+
+    private void traceEnd(String msg) {
+        Trace.traceEnd(TRACE_TAG);
+        if (DEBUG) Slog.d(TAG, "traceEnd-" + msg);
     }
 }
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index d369af6..e4a5a3e 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -402,11 +402,15 @@
     public enum FrameRate {
         FPS_DEFAULT(0),
         FPS_30(30),
+        FPS_36(36),
         FPS_40(40),
         FPS_45(45),
+        FPS_48(48),
         FPS_60(60),
+        FPS_72(72),
         FPS_90(90),
         FPS_120(120),
+        FPS_144(144),
         FPS_INVALID(-1);
 
         public final int fps;
@@ -423,16 +427,24 @@
         switch (raw) {
             case "30":
                 return FrameRate.FPS_30.fps;
+            case "36":
+                return FrameRate.FPS_36.fps;
             case "40":
                 return FrameRate.FPS_40.fps;
             case "45":
                 return FrameRate.FPS_45.fps;
+            case "48":
+                return FrameRate.FPS_48.fps;
             case "60":
                 return FrameRate.FPS_60.fps;
+            case "72":
+                return FrameRate.FPS_72.fps;
             case "90":
                 return FrameRate.FPS_90.fps;
             case "120":
                 return FrameRate.FPS_120.fps;
+            case "144":
+                return FrameRate.FPS_144.fps;
             case "disable":
             case "":
                 return FrameRate.FPS_DEFAULT.fps;
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 9429b4c..4984f12 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -303,8 +303,6 @@
                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
         mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLE_BROADCAST,
                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
-        mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_HEARING_AID,
-                SAFE_MEDIA_VOLUME_UNINITIALIZED);
         mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
         // TODO(b/278265907): enable A2DP when we can distinguish A2DP headsets
diff --git a/services/core/java/com/android/server/display/color/CctEvaluator.java b/services/core/java/com/android/server/display/color/CctEvaluator.java
new file mode 100644
index 0000000..878f7e5
--- /dev/null
+++ b/services/core/java/com/android/server/display/color/CctEvaluator.java
@@ -0,0 +1,108 @@
+/*
+ * 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.color;
+
+import android.animation.TypeEvaluator;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * Interpolates between CCT values by a given step.
+ */
+class CctEvaluator implements TypeEvaluator<Integer> {
+
+    private static final String TAG = "CctEvaluator";
+
+    /**
+     * The minimum input value, which will represent index 0 in the mValues array. Each
+     * subsequent input value is offset by this amount.
+     */
+    private final int mIndexOffset;
+    /**
+     * Cached step values at each CCT value (offset by the {@link #mIndexOffset} above). For
+     * example, if the minimum CCT is 2000K (which is set to mIndexOffset), then the 0th index of
+     * this array is equivalent to the step value at 2000K, 1st index corresponds to 2001K, and so
+     * on.
+     */
+    @VisibleForTesting
+    final int[] mStepsAtOffsetCcts;
+    /**
+     * Pre-computed stepped CCTs. These will be accessed frequently; the memory cost of caching them
+     * is well-spent.
+     */
+    @VisibleForTesting
+    final int[] mSteppedCctsAtOffsetCcts;
+
+    CctEvaluator(int min, int max, int[] cctRangeMinimums, int[] steps) {
+        final int delta = max - min + 1;
+        mStepsAtOffsetCcts = new int[delta];
+        mSteppedCctsAtOffsetCcts = new int[delta];
+        mIndexOffset = min;
+
+        final int parallelArraysLength = cctRangeMinimums.length;
+        if (cctRangeMinimums.length != steps.length) {
+            Slog.e(TAG,
+                    "Parallel arrays cctRangeMinimums and steps are different lengths; setting "
+                            + "step of 1");
+            setStepOfOne();
+        } else if (parallelArraysLength == 0) {
+            Slog.e(TAG, "No cctRangeMinimums or steps are set; setting step of 1");
+            setStepOfOne();
+        } else {
+            int parallelArraysIndex = 0;
+            int index = 0;
+            int lastSteppedCct = Integer.MIN_VALUE;
+            while (index < delta) {
+                final int cct = index + mIndexOffset;
+                int nextParallelArraysIndex = parallelArraysIndex + 1;
+                while (nextParallelArraysIndex < parallelArraysLength
+                        && cct >= cctRangeMinimums[nextParallelArraysIndex]) {
+                    parallelArraysIndex = nextParallelArraysIndex;
+                    nextParallelArraysIndex++;
+                }
+                mStepsAtOffsetCcts[index] = steps[parallelArraysIndex];
+                if (lastSteppedCct == Integer.MIN_VALUE
+                        || Math.abs(lastSteppedCct - cct) >= steps[parallelArraysIndex]) {
+                    lastSteppedCct = cct;
+                }
+                mSteppedCctsAtOffsetCcts[index] = lastSteppedCct;
+                index++;
+            }
+        }
+    }
+
+    @Override
+    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
+        final int cct = (int) (startValue + fraction * (endValue - startValue));
+        final int index = cct - mIndexOffset;
+        if (index < 0 || index >= mSteppedCctsAtOffsetCcts.length) {
+            Slog.e(TAG, "steppedCctValueAt: returning same since invalid requested index=" + index);
+            return cct;
+        }
+        return mSteppedCctsAtOffsetCcts[index];
+    }
+
+    private void setStepOfOne() {
+        Arrays.fill(mStepsAtOffsetCcts, 1);
+        for (int i = 0; i < mSteppedCctsAtOffsetCcts.length; i++) {
+            mSteppedCctsAtOffsetCcts[i] = mIndexOffset + i;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 0284d9c..c0ea5fea 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -180,6 +180,8 @@
      */
     private SparseIntArray mColorModeCompositionColorSpaces = null;
 
+    private final Object mCctTintApplierLock = new Object();
+
     public ColorDisplayService(Context context) {
         super(context);
         mHandler = new TintHandler(DisplayThread.get().getLooper());
@@ -698,6 +700,79 @@
         }
     }
 
+    private void applyTintByCct(ColorTemperatureTintController tintController, boolean immediate) {
+        synchronized (mCctTintApplierLock) {
+            tintController.cancelAnimator();
+
+            final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
+            final int from = tintController.getAppliedCct();
+            final int to = tintController.isActivated() ? tintController.getTargetCct()
+                    : tintController.getDisabledCct();
+
+            if (immediate) {
+                Slog.d(TAG, tintController.getClass().getSimpleName()
+                        + " applied immediately: toCct=" + to + " fromCct=" + from);
+                dtm.setColorMatrix(tintController.getLevel(),
+                        tintController.computeMatrixForCct(to));
+                tintController.setAppliedCct(to);
+            } else {
+                Slog.d(TAG, tintController.getClass().getSimpleName() + " animation started: toCct="
+                        + to + " fromCct=" + from);
+                ValueAnimator valueAnimator = ValueAnimator.ofInt(from, to);
+                tintController.setAnimator(valueAnimator);
+                final CctEvaluator evaluator = tintController.getEvaluator();
+                if (evaluator != null) {
+                    valueAnimator.setEvaluator(evaluator);
+                }
+                valueAnimator.setDuration(tintController.getTransitionDurationMilliseconds());
+                valueAnimator.setInterpolator(AnimationUtils.loadInterpolator(
+                        getContext(), android.R.interpolator.linear));
+                valueAnimator.addUpdateListener((ValueAnimator animator) -> {
+                    synchronized (mCctTintApplierLock) {
+                        final int value = (int) animator.getAnimatedValue();
+                        if (value != tintController.getAppliedCct()) {
+                            dtm.setColorMatrix(tintController.getLevel(),
+                                    tintController.computeMatrixForCct(value));
+                            tintController.setAppliedCct(value);
+                        }
+                    }
+                });
+                valueAnimator.addListener(new AnimatorListenerAdapter() {
+
+                    private boolean mIsCancelled;
+
+                    @Override
+                    public void onAnimationCancel(Animator animator) {
+                        Slog.d(TAG, tintController.getClass().getSimpleName()
+                                + " animation cancelled");
+                        mIsCancelled = true;
+                    }
+
+                    @Override
+                    public void onAnimationEnd(Animator animator) {
+                        synchronized (mCctTintApplierLock) {
+                            Slog.d(TAG, tintController.getClass().getSimpleName()
+                                    + " animation ended: wasCancelled=" + mIsCancelled
+                                    + " toCct=" + to
+                                    + " fromCct=" + from);
+                            if (!mIsCancelled) {
+                                // Ensure final color matrix is set at the end of the animation.
+                                // If the animation is cancelled then don't set the final color
+                                // matrix so the new animator can pick up from where this one left
+                                // off.
+                                dtm.setColorMatrix(tintController.getLevel(),
+                                        tintController.computeMatrixForCct(to));
+                                tintController.setAppliedCct(to);
+                            }
+                            tintController.setAnimator(null);
+                        }
+                    }
+                });
+                valueAnimator.start();
+            }
+        }
+    }
+
     /**
      * Returns the first date time corresponding to the local time that occurs before the provided
      * date time.
@@ -747,7 +822,7 @@
 
         // If disabled, clear the tint. If enabled, do nothing more here and let the next
         // temperature update set the correct tint.
-        if (!activated) {
+        if (oldActivated && !activated) {
             mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_WHITE_BALANCE);
         }
     }
@@ -1452,7 +1527,7 @@
     public class ColorDisplayServiceInternal {
 
         /** Sets whether DWB should be allowed in the current state. */
-        public void setDisplayWhiteBalanceAllowed(boolean allowed)  {
+        public void setDisplayWhiteBalanceAllowed(boolean allowed) {
             mDisplayWhiteBalanceTintController.setAllowed(allowed);
             updateDisplayWhiteBalanceStatus();
         }
@@ -1464,8 +1539,8 @@
          * @param cct the color temperature in Kelvin.
          */
         public boolean setDisplayWhiteBalanceColorTemperature(int cct) {
-            // Update the transform matrix even if it can't be applied.
-            mDisplayWhiteBalanceTintController.setMatrix(cct);
+            // Update the transform target CCT even if it can't be applied.
+            mDisplayWhiteBalanceTintController.setTargetCct(cct);
 
             if (mDisplayWhiteBalanceTintController.isActivated()) {
                 mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_WHITE_BALANCE);
@@ -1601,7 +1676,7 @@
                     applyTint(mNightDisplayTintController, false);
                     break;
                 case MSG_APPLY_DISPLAY_WHITE_BALANCE:
-                    applyTint(mDisplayWhiteBalanceTintController, false);
+                    applyTintByCct(mDisplayWhiteBalanceTintController, false);
                     break;
             }
         }
diff --git a/services/core/java/com/android/server/display/color/ColorTemperatureTintController.java b/services/core/java/com/android/server/display/color/ColorTemperatureTintController.java
new file mode 100644
index 0000000..0fbd9d4
--- /dev/null
+++ b/services/core/java/com/android/server/display/color/ColorTemperatureTintController.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.color;
+
+abstract class ColorTemperatureTintController extends TintController {
+
+    abstract int getAppliedCct();
+
+    abstract void setAppliedCct(int cct);
+
+    abstract int getTargetCct();
+
+    abstract void setTargetCct(int cct);
+
+    /**
+     * Returns the CCT value most closely associated with the "disabled" (identity) matrix for
+     * this device, to use as the target when deactivating this transform.
+     */
+    abstract int getDisabledCct();
+
+    abstract float[] computeMatrixForCct(int cct);
+
+    abstract CctEvaluator getEvaluator();
+}
diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
index 106ac0c..bf0139f 100644
--- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
+++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
@@ -21,6 +21,7 @@
 import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.Size;
 import android.content.Context;
 import android.content.res.Resources;
@@ -36,7 +37,7 @@
 
 import java.io.PrintWriter;
 
-final class DisplayWhiteBalanceTintController extends TintController {
+final class DisplayWhiteBalanceTintController extends ColorTemperatureTintController {
 
     // Three chromaticity coordinates per color: X, Y, and Z
     private static final int NUM_VALUES_PER_PRIMARY = 3;
@@ -52,9 +53,11 @@
     private int mTemperatureDefault;
     @VisibleForTesting
     float[] mDisplayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
+    private int mDisplayNominalWhiteCct;
     @VisibleForTesting
     ColorSpace.Rgb mDisplayColorSpaceRGB;
     private float[] mChromaticAdaptationMatrix;
+    // The temperature currently represented in the matrix.
     @VisibleForTesting
     int mCurrentColorTemperature;
     private float[] mCurrentColorTemperatureXYZ;
@@ -65,6 +68,9 @@
     private Boolean mIsAvailable;
     // This feature becomes disallowed if the device is in an unsupported strong/light state.
     private boolean mIsAllowed = true;
+    private int mTargetCct;
+    private int mAppliedCct;
+    private CctEvaluator mCctEvaluator;
 
     private final DisplayManagerInternal mDisplayManagerInternal;
 
@@ -108,6 +114,9 @@
             displayNominalWhiteXYZ[i] = Float.parseFloat(nominalWhiteValues[i]);
         }
 
+        final int displayNominalWhiteCct = res.getInteger(
+                R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
+
         final int colorTemperatureMin = res.getInteger(
                 R.integer.config_displayWhiteBalanceColorTemperatureMin);
         if (colorTemperatureMin <= 0) {
@@ -124,19 +133,28 @@
             return;
         }
 
-        final int colorTemperature = res.getInteger(
+        final int defaultTemperature = res.getInteger(
                 R.integer.config_displayWhiteBalanceColorTemperatureDefault);
 
         mTransitionDuration = res.getInteger(
                 R.integer.config_displayWhiteBalanceTransitionTime);
 
+        int[] cctRangeMinimums = res.getIntArray(
+                R.array.config_displayWhiteBalanceDisplayRangeMinimums);
+        int[] steps = res.getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
+
         synchronized (mLock) {
             mDisplayColorSpaceRGB = displayColorSpaceRGB;
             mDisplayNominalWhiteXYZ = displayNominalWhiteXYZ;
+            mDisplayNominalWhiteCct = displayNominalWhiteCct;
+            mTargetCct = mDisplayNominalWhiteCct;
+            mAppliedCct = mDisplayNominalWhiteCct;
             mTemperatureMin = colorTemperatureMin;
             mTemperatureMax = colorTemperatureMax;
-            mTemperatureDefault = colorTemperature;
+            mTemperatureDefault = defaultTemperature;
             mSetUp = true;
+            mCctEvaluator = new CctEvaluator(mTemperatureMin, mTemperatureMax,
+                    cctRangeMinimums, steps);
         }
 
         setMatrix(mTemperatureDefault);
@@ -144,8 +162,16 @@
 
     @Override
     public float[] getMatrix() {
-        return mSetUp && isActivated() ? mMatrixDisplayWhiteBalance
-                : ColorDisplayService.MATRIX_IDENTITY;
+        if (!mSetUp || !isActivated()) {
+            return ColorDisplayService.MATRIX_IDENTITY;
+        }
+        computeMatrixForCct(mAppliedCct);
+        return mMatrixDisplayWhiteBalance;
+    }
+
+    @Override
+    public int getTargetCct() {
+        return mTargetCct;
     }
 
     /**
@@ -174,6 +200,12 @@
 
     @Override
     public void setMatrix(int cct) {
+        setTargetCct(cct);
+        computeMatrixForCct(mTargetCct);
+    }
+
+    @Override
+    public void setTargetCct(int cct) {
         if (!mSetUp) {
             Slog.w(ColorDisplayService.TAG,
                     "Can't set display white balance temperature: uninitialized");
@@ -183,50 +215,93 @@
         if (cct < mTemperatureMin) {
             Slog.w(ColorDisplayService.TAG,
                     "Requested display color temperature is below allowed minimum");
-            cct = mTemperatureMin;
+            mTargetCct = mTemperatureMin;
         } else if (cct > mTemperatureMax) {
             Slog.w(ColorDisplayService.TAG,
                     "Requested display color temperature is above allowed maximum");
-            cct = mTemperatureMax;
+            mTargetCct = mTemperatureMax;
+        } else {
+            mTargetCct = cct;
+        }
+    }
+
+    @Override
+    public int getDisabledCct() {
+        return mDisplayNominalWhiteCct;
+    }
+
+    @Override
+    public float[] computeMatrixForCct(int cct) {
+        if (!mSetUp || cct == 0) {
+            Slog.w(ColorDisplayService.TAG, "Couldn't compute matrix for cct=" + cct);
+            return ColorDisplayService.MATRIX_IDENTITY;
         }
 
         synchronized (mLock) {
             mCurrentColorTemperature = cct;
 
-            // Adapt the display's nominal white point to match the requested CCT value
-            mCurrentColorTemperatureXYZ = ColorSpace.cctToXyz(cct);
-
-            mChromaticAdaptationMatrix =
-                    ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
-                            mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
-
-            // Convert the adaptation matrix to RGB space
-            float[] result = mul3x3(mChromaticAdaptationMatrix,
-                    mDisplayColorSpaceRGB.getTransform());
-            result = mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result);
-
-            // Normalize the transform matrix to peak white value in RGB space
-            final float adaptedMaxR = result[0] + result[3] + result[6];
-            final float adaptedMaxG = result[1] + result[4] + result[7];
-            final float adaptedMaxB = result[2] + result[5] + result[8];
-            final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB);
-
-            Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
-            for (int i = 0; i < result.length; i++) {
-                result[i] /= denum;
-                if (!isColorMatrixCoeffValid(result[i])) {
-                    Slog.e(ColorDisplayService.TAG, "Invalid DWB color matrix");
-                    return;
-                }
+            if (cct == mDisplayNominalWhiteCct && !isActivated()) {
+                // DWB is finished turning off. Clear the matrix.
+                Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
+            } else {
+                computeMatrixForCctLocked(cct);
             }
 
-            java.lang.System.arraycopy(result, 0, mMatrixDisplayWhiteBalance, 0, 3);
-            java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3);
-            java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3);
+            Slog.d(ColorDisplayService.TAG, "computeDisplayWhiteBalanceMatrix: cct =" + cct
+                    + " matrix =" + matrixToString(mMatrixDisplayWhiteBalance, 16));
+
+            return mMatrixDisplayWhiteBalance;
+        }
+    }
+
+    private void computeMatrixForCctLocked(int cct) {
+        // Adapt the display's nominal white point to match the requested CCT value
+        mCurrentColorTemperatureXYZ = ColorSpace.cctToXyz(cct);
+
+        mChromaticAdaptationMatrix =
+                ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
+                        mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
+
+        // Convert the adaptation matrix to RGB space
+        float[] result = mul3x3(mChromaticAdaptationMatrix,
+                mDisplayColorSpaceRGB.getTransform());
+        result = mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result);
+
+        // Normalize the transform matrix to peak white value in RGB space
+        final float adaptedMaxR = result[0] + result[3] + result[6];
+        final float adaptedMaxG = result[1] + result[4] + result[7];
+        final float adaptedMaxB = result[2] + result[5] + result[8];
+        final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB);
+
+        Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
+
+        for (int i = 0; i < result.length; i++) {
+            result[i] /= denum;
+            if (!isColorMatrixCoeffValid(result[i])) {
+                Slog.e(ColorDisplayService.TAG, "Invalid DWB color matrix");
+                return;
+            }
         }
 
-        Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct
-                + " matrix = " + matrixToString(mMatrixDisplayWhiteBalance, 16));
+        java.lang.System.arraycopy(result, 0, mMatrixDisplayWhiteBalance, 0, 3);
+        java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3);
+        java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3);
+    }
+
+    @Override
+    int getAppliedCct() {
+        return mAppliedCct;
+    }
+
+    @Override
+    void setAppliedCct(int cct) {
+        mAppliedCct = cct;
+    }
+
+    @Override
+    @Nullable
+    CctEvaluator getEvaluator() {
+        return mCctEvaluator;
     }
 
     @Override
@@ -258,7 +333,10 @@
             pw.println("    mTemperatureMin = " + mTemperatureMin);
             pw.println("    mTemperatureMax = " + mTemperatureMax);
             pw.println("    mTemperatureDefault = " + mTemperatureDefault);
+            pw.println("    mDisplayNominalWhiteCct = " + mDisplayNominalWhiteCct);
             pw.println("    mCurrentColorTemperature = " + mCurrentColorTemperature);
+            pw.println("    mTargetCct = " + mTargetCct);
+            pw.println("    mAppliedCct = " + mAppliedCct);
             pw.println("    mCurrentColorTemperatureXYZ = "
                     + matrixToString(mCurrentColorTemperatureXYZ, 3));
             pw.println("    mDisplayColorSpaceRGB RGB-to-XYZ = "
@@ -340,11 +418,7 @@
     }
 
     private boolean isColorMatrixCoeffValid(float coeff) {
-        if (Float.isNaN(coeff) || Float.isInfinite(coeff)) {
-            return false;
-        }
-
-        return true;
+        return !Float.isNaN(coeff) && !Float.isInfinite(coeff);
     }
 
     private boolean isColorMatrixValid(float[] matrix) {
@@ -352,8 +426,8 @@
             return false;
         }
 
-        for (int i = 0; i < matrix.length; i++) {
-            if (!isColorMatrixCoeffValid(matrix[i])) {
+        for (float value : matrix) {
+            if (!isColorMatrixCoeffValid(value)) {
                 return false;
             }
         }
diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java
index c53ac06..384333a 100644
--- a/services/core/java/com/android/server/display/color/TintController.java
+++ b/services/core/java/com/android/server/display/color/TintController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display.color;
 
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.util.Slog;
 
@@ -28,14 +29,14 @@
      */
     private static final long TRANSITION_DURATION = 3000L;
 
-    private ColorDisplayService.TintValueAnimator mAnimator;
+    private ValueAnimator mAnimator;
     private Boolean mIsActivated;
 
-    public ColorDisplayService.TintValueAnimator getAnimator() {
+    public ValueAnimator getAnimator() {
         return mAnimator;
     }
 
-    public void setAnimator(ColorDisplayService.TintValueAnimator animator) {
+    public void setAnimator(ValueAnimator animator) {
         mAnimator = animator;
     }
 
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index d8716b3..6ec4022 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -562,7 +562,7 @@
                 key.append(",languageTag:").append(inputDevice.getKeyboardLanguageTag());
             }
             if (!TextUtils.isEmpty(inputDevice.getKeyboardLayoutType())) {
-                key.append(",layoutType:").append(inputDevice.getKeyboardLanguageTag());
+                key.append(",layoutType:").append(inputDevice.getKeyboardLayoutType());
             }
         }
         return key.toString();
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 464a256..02b7053 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -16,6 +16,12 @@
 
 package com.android.server.media;
 
+import static android.media.VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+import static android.media.VolumeProvider.VOLUME_CONTROL_FIXED;
+import static android.media.VolumeProvider.VOLUME_CONTROL_RELATIVE;
+import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -173,8 +179,8 @@
     // Volume handling fields
     private AudioAttributes mAudioAttrs;
     private AudioManager mAudioManager;
-    private int mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
-    private int mVolumeControlType = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+    private int mVolumeType = PLAYBACK_TYPE_LOCAL;
+    private int mVolumeControlType = VOLUME_CONTROL_ABSOLUTE;
     private int mMaxVolume = 0;
     private int mCurrentVolume = 0;
     private int mOptimisticVolume = -1;
@@ -309,13 +315,13 @@
         if (checkPlaybackActiveState(true) || isSystemPriority()) {
             flags &= ~AudioManager.FLAG_PLAY_SOUND;
         }
-        if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
+        if (mVolumeType == PLAYBACK_TYPE_LOCAL) {
             // Adjust the volume with a handler not to be blocked by other system service.
             int stream = getVolumeStream(mAudioAttrs);
             postAdjustLocalVolume(stream, direction, flags, opPackageName, pid, uid,
                     asSystemService, useSuggested, previousFlagPlaySound);
         } else {
-            if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
+            if (mVolumeControlType == VOLUME_CONTROL_FIXED) {
                 if (DEBUG) {
                     Log.d(TAG, "Session does not support volume adjustment");
                 }
@@ -354,7 +360,7 @@
 
     private void setVolumeTo(String packageName, String opPackageName, int pid, int uid, int value,
             int flags) {
-        if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
+        if (mVolumeType == PLAYBACK_TYPE_LOCAL) {
             int stream = getVolumeStream(mAudioAttrs);
             final int volumeValue = value;
             mHandler.post(new Runnable() {
@@ -371,7 +377,7 @@
                 }
             });
         } else {
-            if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
+            if (mVolumeControlType != VOLUME_CONTROL_ABSOLUTE) {
                 if (DEBUG) {
                     Log.d(TAG, "Session does not support setting volume");
                 }
@@ -433,7 +439,7 @@
      */
     @Override
     public boolean isPlaybackTypeLocal() {
-        return mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+        return mVolumeType == PLAYBACK_TYPE_LOCAL;
     }
 
     @Override
@@ -495,7 +501,7 @@
 
     @Override
     public boolean canHandleVolumeKey() {
-        return mVolumeControlType != VolumeProvider.VOLUME_CONTROL_FIXED;
+        return mVolumeControlType != VOLUME_CONTROL_FIXED;
     }
 
     @Override
@@ -528,13 +534,48 @@
         pw.println(indent + "controllers: " + mControllerCallbackHolders.size());
         pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString()));
         pw.println(indent + "audioAttrs=" + mAudioAttrs);
-        pw.println(indent + "volumeType=" + mVolumeType + ", controlType=" + mVolumeControlType
-                + ", max=" + mMaxVolume + ", current=" + mCurrentVolume);
+        pw.append(indent)
+                .append("volumeType=")
+                .append(toVolumeTypeString(mVolumeType))
+                .append(", controlType=")
+                .append(toVolumeControlTypeString(mVolumeControlType))
+                .append(", max=")
+                .append(Integer.toString(mMaxVolume))
+                .append(", current=")
+                .append(Integer.toString(mCurrentVolume))
+                .append(", volumeControlId=")
+                .append(mVolumeControlId)
+                .println();
         pw.println(indent + "metadata: " + mMetadataDescription);
         pw.println(indent + "queueTitle=" + mQueueTitle + ", size="
                 + (mQueue == null ? 0 : mQueue.size()));
     }
 
+    private static String toVolumeControlTypeString(
+            @VolumeProvider.ControlType int volumeControlType) {
+        switch (volumeControlType) {
+            case VOLUME_CONTROL_FIXED:
+                return "FIXED";
+            case VOLUME_CONTROL_RELATIVE:
+                return "RELATIVE";
+            case VOLUME_CONTROL_ABSOLUTE:
+                return "ABSOLUTE";
+            default:
+                return TextUtils.formatSimple("unknown(%d)", volumeControlType);
+        }
+    }
+
+    private static String toVolumeTypeString(@PlaybackInfo.PlaybackType int volumeType) {
+        switch (volumeType) {
+            case PLAYBACK_TYPE_LOCAL:
+                return "LOCAL";
+            case PLAYBACK_TYPE_REMOTE:
+                return "REMOTE";
+            default:
+                return TextUtils.formatSimple("unknown(%d)", volumeType);
+        }
+    }
+
     @Override
     public String toString() {
         return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
@@ -877,8 +918,8 @@
         int stream = getVolumeStream(attributes);
         int max = mAudioManager.getStreamMaxVolume(stream);
         int current = mAudioManager.getStreamVolume(stream);
-        return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max,
-                current, attributes, null);
+        return new PlaybackInfo(
+                volumeType, VOLUME_CONTROL_ABSOLUTE, max, current, attributes, null);
     }
 
     private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
@@ -1124,7 +1165,7 @@
             boolean typeChanged;
             synchronized (mLock) {
                 typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
-                mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+                mVolumeType = PLAYBACK_TYPE_LOCAL;
                 mVolumeControlId = null;
                 if (attributes != null) {
                     mAudioAttrs = attributes;
@@ -1148,7 +1189,7 @@
                 throws RemoteException {
             boolean typeChanged;
             synchronized (mLock) {
-                typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+                typeChanged = mVolumeType == PLAYBACK_TYPE_LOCAL;
                 mVolumeType = PlaybackInfo.PLAYBACK_TYPE_REMOTE;
                 mVolumeControlType = control;
                 mMaxVolume = max;
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index a267e8a..5932929 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -4360,12 +4360,22 @@
 
         // A new application appeared on /system, and we are seeing it for the first time.
         // Its also not updated as we don't have a copy of it on /data. So, scan it in a
-        // STOPPED state. Ignore if it's an APEX package since stopped state does not affect them.
+        // STOPPED state.
+        // We'll skip this step under the following conditions:
+        //   - It's "android"
+        //   - It's an APEX or overlay package since stopped state does not affect them.
+        //   - It is enumerated with a <initial-package-state> tag having the stopped attribute
+        //     set to false
         final boolean isApexPkg = (scanFlags & SCAN_AS_APEX) != 0;
-        if (mPm.mShouldStopSystemPackagesByDefault && scanSystemPartition
-                && !pkgAlreadyExists && !isApexPkg) {
+        if (mPm.mShouldStopSystemPackagesByDefault
+                && scanSystemPartition
+                && !pkgAlreadyExists
+                && !isApexPkg
+                && !parsedPackage.isOverlayIsStatic()
+        ) {
             String packageName = parsedPackage.getPackageName();
-            if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName)) {
+            if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName)
+                    && !"android".contentEquals(packageName)) {
                 scanFlags |= SCAN_AS_STOPPED_SYSTEM_APP;
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b5108af..f482046 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5258,6 +5258,12 @@
         }
 
         @Override
+        public @NonNull List<String> getInitialNonStoppedSystemPackages() {
+            return mInitialNonStoppedSystemPackages != null
+                    ? new ArrayList<>(mInitialNonStoppedSystemPackages) : new ArrayList<>();
+        }
+
+        @Override
         public String[] getUnsuspendablePackagesForUser(String[] packageNames, int userId) {
             Objects.requireNonNull(packageNames, "packageNames cannot be null");
             mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index f78b611..58183f0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -782,6 +782,8 @@
                         getInFileDescriptor(), getOutFileDescriptor(), getErrFileDescriptor(),
                         new String[] { "list" }, getShellCallback(), adoptResultReceiver());
                 return 0;
+            case "initial-non-stopped-system-packages":
+                return runListInitialNonStoppedSystemPackages();
         }
         pw.println("Error: unknown list type '" + type + "'");
         return -1;
@@ -794,6 +796,21 @@
         return 0;
     }
 
+    private int runListInitialNonStoppedSystemPackages() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final List<String> list = mInterface.getInitialNonStoppedSystemPackages();
+
+        Collections.sort(list);
+
+        for (String pkgName : list) {
+            pw.print("package:");
+            pw.print(pkgName);
+            pw.println();
+        }
+
+        return 0;
+    }
+
     private int runListFeatures() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
         final List<FeatureInfo> list = mInterface.getSystemAvailableFeatures().getList();
diff --git a/services/core/java/com/android/server/pm/UserJourneyLogger.java b/services/core/java/com/android/server/pm/UserJourneyLogger.java
new file mode 100644
index 0000000..f48a166
--- /dev/null
+++ b/services/core/java/com/android/server/pm/UserJourneyLogger.java
@@ -0,0 +1,552 @@
+/*
+ * 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.pm;
+
+import static android.os.UserManager.USER_TYPE_FULL_DEMO;
+import static android.os.UserManager.USER_TYPE_FULL_GUEST;
+import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED;
+import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
+import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
+import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
+import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
+import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
+
+import static com.android.internal.util.FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.UserInfo;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * This class is logging User Lifecycle statsd events and synchronise User Lifecycle Journeys
+ * by making sure all events are called in correct order and errors are reported in case of
+ * unexpected journeys. This class also makes sure that all user sub-journeys are logged so
+ * for example User Switch also log User Start Journey.
+ */
+public class UserJourneyLogger {
+
+    public static final int ERROR_CODE_INVALID_SESSION_ID = 0;
+    public static final int ERROR_CODE_UNSPECIFIED = -1;
+    /*
+     * Possible reasons for ERROR_CODE_INCOMPLETE_OR_TIMEOUT to occur:
+     * - A user switch journey is received while another user switch journey is in
+     *   process for the same user.
+     * - A user switch journey is received while user start journey is in process for
+     *   the same user.
+     * - A user start journey is received while another user start journey is in process
+     *   for the same user.
+     * In all cases potentially an incomplete, timed-out session or multiple
+     * simultaneous requests. It is not possible to keep track of multiple sessions for
+     * the same user, so previous session is abandoned.
+     */
+    public static final int ERROR_CODE_INCOMPLETE_OR_TIMEOUT = 2;
+    public static final int ERROR_CODE_ABORTED = 3;
+    public static final int ERROR_CODE_NULL_USER_INFO = 4;
+    public static final int ERROR_CODE_USER_ALREADY_AN_ADMIN = 5;
+    public static final int ERROR_CODE_USER_IS_NOT_AN_ADMIN = 6;
+
+    @IntDef(prefix = {"ERROR_CODE"}, value = {
+            ERROR_CODE_UNSPECIFIED,
+            ERROR_CODE_INCOMPLETE_OR_TIMEOUT,
+            ERROR_CODE_ABORTED,
+            ERROR_CODE_NULL_USER_INFO,
+            ERROR_CODE_USER_ALREADY_AN_ADMIN,
+            ERROR_CODE_USER_IS_NOT_AN_ADMIN,
+            ERROR_CODE_INVALID_SESSION_ID
+    })
+    public @interface UserJourneyErrorCode {
+    }
+
+    // The various user journeys, defined in the UserLifecycleJourneyReported atom for statsd
+    public static final int USER_JOURNEY_UNKNOWN =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__UNKNOWN;
+    public static final int USER_JOURNEY_USER_SWITCH_FG =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_FG;
+    public static final int USER_JOURNEY_USER_SWITCH_UI =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_UI;
+    public static final int USER_JOURNEY_USER_START =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_START;
+    public static final int USER_JOURNEY_USER_CREATE =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE;
+    public static final int USER_JOURNEY_USER_STOP =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_STOP;
+    public static final int USER_JOURNEY_USER_REMOVE =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE;
+    public static final int USER_JOURNEY_GRANT_ADMIN =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN;
+    public static final int USER_JOURNEY_REVOKE_ADMIN =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN;
+
+    @IntDef(prefix = {"USER_JOURNEY"}, value = {
+            USER_JOURNEY_UNKNOWN,
+            USER_JOURNEY_USER_SWITCH_FG,
+            USER_JOURNEY_USER_SWITCH_UI,
+            USER_JOURNEY_USER_START,
+            USER_JOURNEY_USER_STOP,
+            USER_JOURNEY_USER_CREATE,
+            USER_JOURNEY_USER_REMOVE,
+            USER_JOURNEY_GRANT_ADMIN,
+            USER_JOURNEY_REVOKE_ADMIN
+    })
+    public @interface UserJourney {
+    }
+
+
+    // The various user lifecycle events, defined in the UserLifecycleEventOccurred atom for statsd
+    public static final int USER_LIFECYCLE_EVENT_UNKNOWN =
+            USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN;
+    public static final int USER_LIFECYCLE_EVENT_SWITCH_USER =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__SWITCH_USER;
+    public static final int USER_LIFECYCLE_EVENT_START_USER =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__START_USER;
+    public static final int USER_LIFECYCLE_EVENT_CREATE_USER =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
+    public static final int USER_LIFECYCLE_EVENT_REMOVE_USER =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REMOVE_USER;
+    public static final int USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__USER_RUNNING_LOCKED;
+    public static final int USER_LIFECYCLE_EVENT_UNLOCKING_USER =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKING_USER;
+    public static final int USER_LIFECYCLE_EVENT_UNLOCKED_USER =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKED_USER;
+    public static final int USER_LIFECYCLE_EVENT_STOP_USER =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__STOP_USER;
+    public static final int USER_LIFECYCLE_EVENT_GRANT_ADMIN =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
+    public static final int USER_LIFECYCLE_EVENT_REVOKE_ADMIN =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
+
+    @IntDef(prefix = {"USER_LIFECYCLE_EVENT"}, value = {
+            USER_LIFECYCLE_EVENT_UNKNOWN,
+            USER_LIFECYCLE_EVENT_SWITCH_USER,
+            USER_LIFECYCLE_EVENT_START_USER,
+            USER_LIFECYCLE_EVENT_CREATE_USER,
+            USER_LIFECYCLE_EVENT_REMOVE_USER,
+            USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
+            USER_LIFECYCLE_EVENT_UNLOCKING_USER,
+            USER_LIFECYCLE_EVENT_UNLOCKED_USER,
+            USER_LIFECYCLE_EVENT_STOP_USER,
+            USER_LIFECYCLE_EVENT_GRANT_ADMIN,
+            USER_LIFECYCLE_EVENT_REVOKE_ADMIN
+    })
+    public @interface UserLifecycleEvent {
+    }
+
+    // User lifecycle event state, defined in the UserLifecycleEventOccurred atom for statsd
+    public static final int EVENT_STATE_BEGIN =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN;
+    public static final int EVENT_STATE_FINISH =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH;
+    public static final int EVENT_STATE_NONE =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE;
+    public static final int EVENT_STATE_CANCEL =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__CANCEL;
+    public static final int EVENT_STATE_ERROR =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__ERROR;
+
+    @IntDef(prefix = {"EVENT_STATE"}, value = {
+            EVENT_STATE_BEGIN,
+            EVENT_STATE_FINISH,
+            EVENT_STATE_NONE,
+            EVENT_STATE_CANCEL,
+            EVENT_STATE_ERROR,
+    })
+    public @interface UserLifecycleEventState {
+    }
+
+    private static final int USER_ID_KEY_MULTIPLICATION = 100;
+
+    private final Object mLock = new Object();
+
+    /**
+     * {@link UserIdInt} and {@link UserJourney} to {@link UserJourneySession} mapping used for
+     * statsd logging for the UserLifecycleJourneyReported and UserLifecycleEventOccurred atoms.
+     */
+    @GuardedBy("mLock")
+    private final SparseArray<UserJourneySession> mUserIdToUserJourneyMap = new SparseArray<>();
+
+    /**
+     * Returns event equivalent of given journey
+     */
+    @UserLifecycleEvent
+    private static int journeyToEvent(@UserJourney int journey) {
+        switch (journey) {
+            case USER_JOURNEY_USER_SWITCH_UI:
+            case USER_JOURNEY_USER_SWITCH_FG:
+                return USER_LIFECYCLE_EVENT_SWITCH_USER;
+            case USER_JOURNEY_USER_START:
+                return USER_LIFECYCLE_EVENT_START_USER;
+            case USER_JOURNEY_USER_CREATE:
+                return USER_LIFECYCLE_EVENT_CREATE_USER;
+            case USER_JOURNEY_USER_STOP:
+                return USER_LIFECYCLE_EVENT_STOP_USER;
+            case USER_JOURNEY_USER_REMOVE:
+                return USER_LIFECYCLE_EVENT_REMOVE_USER;
+            case USER_JOURNEY_GRANT_ADMIN:
+                return USER_LIFECYCLE_EVENT_GRANT_ADMIN;
+            case USER_JOURNEY_REVOKE_ADMIN:
+                return USER_LIFECYCLE_EVENT_REVOKE_ADMIN;
+            default:
+                return USER_LIFECYCLE_EVENT_UNKNOWN;
+        }
+    }
+
+    /**
+     * Returns the enum defined in the statsd UserLifecycleJourneyReported atom corresponding to
+     * the user type.
+     * Changes to this method require changes in CTS file
+     * com.android.cts.packagemanager.stats.device.UserInfoUtil
+     * which is duplicate for CTS tests purposes.
+     */
+    public static int getUserTypeForStatsd(@NonNull String userType) {
+        switch (userType) {
+            case USER_TYPE_FULL_SYSTEM:
+                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SYSTEM;
+            case USER_TYPE_FULL_SECONDARY:
+                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY;
+            case USER_TYPE_FULL_GUEST:
+                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_GUEST;
+            case USER_TYPE_FULL_DEMO:
+                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_DEMO;
+            case USER_TYPE_FULL_RESTRICTED:
+                return FrameworkStatsLog
+                        .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_RESTRICTED;
+            case USER_TYPE_PROFILE_MANAGED:
+                return FrameworkStatsLog
+                        .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_MANAGED;
+            case USER_TYPE_SYSTEM_HEADLESS:
+                return FrameworkStatsLog
+                        .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__SYSTEM_HEADLESS;
+            case USER_TYPE_PROFILE_CLONE:
+                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_CLONE;
+            default:
+                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
+        }
+    }
+
+    /**
+     * Map error code to the event finish state.
+     */
+    @UserLifecycleEventState
+    private static int errorToFinishState(@UserJourneyErrorCode int errorCode) {
+        switch (errorCode) {
+            case ERROR_CODE_ABORTED:
+                return EVENT_STATE_CANCEL;
+            case ERROR_CODE_UNSPECIFIED:
+                return EVENT_STATE_FINISH;
+            default:
+                return EVENT_STATE_ERROR;
+        }
+    }
+
+    /**
+     * Simply logging USER_LIFECYCLE_JOURNEY_REPORTED if session exists.
+     * If session does not exist then it logs ERROR_CODE_INVALID_SESSION_ID
+     */
+    @VisibleForTesting
+    public void logUserLifecycleJourneyReported(@Nullable UserJourneySession session,
+            @UserJourney int journey, @UserIdInt int originalUserId, @UserIdInt int targetUserId,
+            int userType, int userFlags, @UserJourneyErrorCode int errorCode) {
+        if (session == null) {
+            writeUserLifecycleJourneyReported(-1, journey, originalUserId, targetUserId,
+                    userType, userFlags, ERROR_CODE_INVALID_SESSION_ID);
+        } else {
+            writeUserLifecycleJourneyReported(
+                    session.mSessionId, journey, originalUserId, targetUserId, userType, userFlags,
+                    errorCode);
+        }
+    }
+
+    /**
+     * Helper method for spy testing
+     */
+    @VisibleForTesting
+    public void writeUserLifecycleJourneyReported(long sessionId, int journey, int originalUserId,
+            int targetUserId, int userType, int userFlags, int errorCode) {
+        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED,
+                sessionId, journey, originalUserId, targetUserId, userType, userFlags,
+                errorCode);
+    }
+
+    /**
+     * Simply logging USER_LIFECYCLE_EVENT_OCCURRED if session exists.
+     * If session does not exist then it logs ERROR_CODE_INVALID_SESSION_ID
+     * and EVENT_STATE_ERROR
+     */
+    @VisibleForTesting
+    public void logUserLifecycleEventOccurred(UserJourneySession session,
+            @UserIdInt int targetUserId, @UserLifecycleEvent int event,
+            @UserLifecycleEventState int state, @UserJourneyErrorCode int errorCode) {
+        if (session == null) {
+            writeUserLifecycleEventOccurred(-1, targetUserId, event,
+                    EVENT_STATE_ERROR, ERROR_CODE_INVALID_SESSION_ID);
+        } else {
+            writeUserLifecycleEventOccurred(session.mSessionId, targetUserId, event, state,
+                    errorCode);
+        }
+    }
+
+    /**
+     * Helper method for spy testing
+     */
+    @VisibleForTesting
+    public void writeUserLifecycleEventOccurred(long sessionId, int userId, int event, int state,
+            int errorCode) {
+        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED,
+                sessionId, userId, event, state, errorCode);
+    }
+
+    /**
+     * statsd helper method for logging the given event for the UserLifecycleEventOccurred statsd
+     * atom. It finds the user journey session for target user id and logs it as that journey.
+     */
+    public void logUserLifecycleEvent(@UserIdInt int userId, @UserLifecycleEvent int event,
+            @UserLifecycleEventState int eventState) {
+        final UserJourneySession userJourneySession = findUserJourneySession(userId);
+        logUserLifecycleEventOccurred(userJourneySession, userId,
+                event, eventState, UserJourneyLogger.ERROR_CODE_UNSPECIFIED);
+    }
+
+    /**
+     * Returns first user session from mUserIdToUserJourneyMap for given user id,
+     * or null if user id was not found in mUserIdToUserJourneyMap.
+     */
+    private @Nullable UserJourneySession findUserJourneySession(@UserIdInt int userId) {
+        synchronized (mLock) {
+            final int keyMapSize = mUserIdToUserJourneyMap.size();
+            for (int i = 0; i < keyMapSize; i++) {
+                int key = mUserIdToUserJourneyMap.keyAt(i);
+                if (key / USER_ID_KEY_MULTIPLICATION == userId) {
+                    return mUserIdToUserJourneyMap.get(key);
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns unique id for user and journey. For example if user id = 11 and journey = 7
+     * then unique key = 11 * 100 + 7 = 1107
+     */
+    private int getUserJourneyKey(@UserIdInt int targetUserId, @UserJourney int journey) {
+        // We leave 99 for user journeys ids.
+        return (targetUserId * USER_ID_KEY_MULTIPLICATION) + journey;
+    }
+
+    /**
+     * Special use case when user journey incomplete or timeout and current user is unclear
+     */
+    @VisibleForTesting
+    public UserJourneySession finishAndClearIncompleteUserJourney(@UserIdInt int targetUserId,
+            @UserJourney int journey) {
+        synchronized (mLock) {
+            final int key = getUserJourneyKey(targetUserId, journey);
+            final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
+            if (userJourneySession != null) {
+                logUserLifecycleEventOccurred(
+                        userJourneySession,
+                        targetUserId,
+                        journeyToEvent(userJourneySession.mJourney),
+                        EVENT_STATE_ERROR,
+                        UserJourneyLogger.ERROR_CODE_INCOMPLETE_OR_TIMEOUT);
+
+                logUserLifecycleJourneyReported(
+                        userJourneySession,
+                        journey,
+                        /* originalUserId= */ -1,
+                        targetUserId,
+                        getUserTypeForStatsd(""), -1,
+                        ERROR_CODE_INCOMPLETE_OR_TIMEOUT);
+                mUserIdToUserJourneyMap.remove(key);
+
+                return userJourneySession;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Log user journey event and report finishing without error
+     */
+    public UserJourneySession logUserJourneyFinish(@UserIdInt int originalUserId,
+            UserInfo targetUser, @UserJourney int journey) {
+        return logUserJourneyFinishWithError(originalUserId, targetUser, journey,
+                ERROR_CODE_UNSPECIFIED);
+    }
+
+    /**
+     * Special case when it is unknown which user switch  journey was used and checking both
+     */
+    @VisibleForTesting
+    public UserJourneySession logUserSwitchJourneyFinish(@UserIdInt int originalUserId,
+            UserInfo targetUser) {
+        synchronized (mLock) {
+            final int key_fg = getUserJourneyKey(targetUser.id, USER_JOURNEY_USER_SWITCH_FG);
+            final int key_ui = getUserJourneyKey(targetUser.id, USER_JOURNEY_USER_SWITCH_UI);
+
+            if (mUserIdToUserJourneyMap.contains(key_fg)) {
+                return logUserJourneyFinish(originalUserId, targetUser,
+                        USER_JOURNEY_USER_SWITCH_FG);
+            }
+
+            if (mUserIdToUserJourneyMap.contains(key_ui)) {
+                return logUserJourneyFinish(originalUserId, targetUser,
+                        USER_JOURNEY_USER_SWITCH_UI);
+            }
+
+            return null;
+        }
+    }
+
+    /**
+     * Log user journey event and report finishing with error
+     */
+    public UserJourneySession logUserJourneyFinishWithError(@UserIdInt int originalUserId,
+            UserInfo targetUser, @UserJourney int journey, @UserJourneyErrorCode int errorCode) {
+        synchronized (mLock) {
+            final int state = errorToFinishState(errorCode);
+            final int key = getUserJourneyKey(targetUser.id, journey);
+            final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
+            if (userJourneySession != null) {
+                logUserLifecycleEventOccurred(
+                        userJourneySession, targetUser.id,
+                        journeyToEvent(userJourneySession.mJourney),
+                        state,
+                        errorCode);
+
+                logUserLifecycleJourneyReported(
+                        userJourneySession,
+                        journey, originalUserId, targetUser.id,
+                        getUserTypeForStatsd(targetUser.userType),
+                        targetUser.flags,
+                        errorCode);
+                mUserIdToUserJourneyMap.remove(key);
+
+                return userJourneySession;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Log event and report finish when user is null. This is edge case when UserInfo
+     * can not be passed because it is null, therefore all information are passed as arguments.
+     */
+    public UserJourneySession logNullUserJourneyError(@UserJourney int journey,
+            @UserIdInt int currentUserId, @UserIdInt int targetUserId, String targetUserType,
+            int targetUserFlags) {
+        synchronized (mLock) {
+            final int key = getUserJourneyKey(targetUserId, journey);
+            final UserJourneySession session = mUserIdToUserJourneyMap.get(key);
+
+            logUserLifecycleEventOccurred(
+                    session, targetUserId, journeyToEvent(journey),
+                    EVENT_STATE_ERROR,
+                    ERROR_CODE_NULL_USER_INFO);
+
+            logUserLifecycleJourneyReported(
+                    session, journey, currentUserId, targetUserId,
+                    getUserTypeForStatsd(targetUserType), targetUserFlags,
+                    ERROR_CODE_NULL_USER_INFO);
+
+            mUserIdToUserJourneyMap.remove(key);
+            return session;
+        }
+    }
+
+    /**
+     * Log for user creation finish event and report. This is edge case when target user id is
+     * different in begin event and finish event as it is unknown what is user id
+     * until it has been created.
+     */
+    public UserJourneySession logUserCreateJourneyFinish(@UserIdInt int originalUserId,
+            UserInfo targetUser) {
+        synchronized (mLock) {
+            // we do not know user id until we create new user which is why we use -1
+            // as user id to create and find session, but we log correct id.
+            final int key = getUserJourneyKey(-1, USER_JOURNEY_USER_CREATE);
+            final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
+            if (userJourneySession != null) {
+                logUserLifecycleEventOccurred(
+                        userJourneySession, targetUser.id,
+                        USER_LIFECYCLE_EVENT_CREATE_USER,
+                        EVENT_STATE_FINISH,
+                        ERROR_CODE_UNSPECIFIED);
+
+                logUserLifecycleJourneyReported(
+                        userJourneySession,
+                        USER_JOURNEY_USER_CREATE, originalUserId, targetUser.id,
+                        getUserTypeForStatsd(targetUser.userType),
+                        targetUser.flags,
+                        ERROR_CODE_UNSPECIFIED);
+                mUserIdToUserJourneyMap.remove(key);
+
+                return userJourneySession;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Adds new UserJourneySession to mUserIdToUserJourneyMap and log UserJourneyEvent Begin state
+     */
+    public UserJourneySession logUserJourneyBegin(@UserIdInt int targetId,
+            @UserJourney int journey) {
+        final long newSessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
+        synchronized (mLock) {
+            final int key = getUserJourneyKey(targetId, journey);
+            final UserJourneySession userJourneySession =
+                    new UserJourneySession(newSessionId, journey);
+            mUserIdToUserJourneyMap.append(key, userJourneySession);
+
+            logUserLifecycleEventOccurred(
+                    userJourneySession, targetId,
+                    journeyToEvent(userJourneySession.mJourney),
+                    EVENT_STATE_BEGIN,
+                    ERROR_CODE_UNSPECIFIED);
+
+            return userJourneySession;
+        }
+    }
+
+    /**
+     * Helper class to store user journey and session id.
+     *
+     * <p> User journey tracks a chain of user lifecycle events occurring during different user
+     * activities such as user start, user switch, and user creation.
+     */
+    public static class UserJourneySession {
+        public final long mSessionId;
+        @UserJourney
+        public final int mJourney;
+
+        @VisibleForTesting
+        public UserJourneySession(long sessionId, @UserJourney int journey) {
+            mJourney = journey;
+            mSessionId = sessionId;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5f8efe2..b92cdde 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -23,6 +23,15 @@
 import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
 import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
 
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_UNSPECIFIED;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_ALREADY_AN_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_IS_NOT_AN_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_GRANT_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_CREATE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_REMOVE;
+
 import android.Manifest;
 import android.accounts.Account;
 import android.accounts.AccountManager;
@@ -162,7 +171,6 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
@@ -519,6 +527,8 @@
     @GuardedBy("mUserLifecycleListeners")
     private final ArrayList<UserLifecycleListener> mUserLifecycleListeners = new ArrayList<>();
 
+    private final UserJourneyLogger mUserJourneyLogger = new UserJourneyLogger();
+
     private final LockPatternUtils mLockPatternUtils;
 
     private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK =
@@ -1580,45 +1590,56 @@
     @Override
     public void setUserAdmin(@UserIdInt int userId) {
         checkManageUserAndAcrossUsersFullPermission("set user admin");
-        final long sessionId = logGrantAdminJourneyBegin(userId);
+        mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_GRANT_ADMIN);
         UserInfo info;
         synchronized (mPackagesLock) {
             synchronized (mUsersLock) {
                 info = getUserInfoLU(userId);
             }
-            if (info == null || info.isAdmin()) {
-                // Exit if no user found with that id, or the user is already an Admin.
-                logUserJourneyError(sessionId,
-                        FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN,
-                        userId);
+            if (info == null) {
+                // Exit if no user found with that id,
+                mUserJourneyLogger.logNullUserJourneyError(USER_JOURNEY_GRANT_ADMIN,
+                        getCurrentUserId(), userId, /* userType */ "", /* userFlags */ -1);
+                return;
+            } else if (info.isAdmin()) {
+                // Exit if the user is already an Admin.
+                mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), info,
+                        USER_JOURNEY_GRANT_ADMIN, ERROR_CODE_USER_ALREADY_AN_ADMIN);
                 return;
             }
             info.flags ^= UserInfo.FLAG_ADMIN;
             writeUserLP(getUserDataLU(info.id));
         }
-        logGrantAdminJourneyFinish(sessionId, userId, info.userType, info.flags);
+        mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), info,
+                USER_JOURNEY_GRANT_ADMIN, ERROR_CODE_UNSPECIFIED);
     }
 
     @Override
     public void revokeUserAdmin(@UserIdInt int userId) {
         checkManageUserAndAcrossUsersFullPermission("revoke admin privileges");
-        final long sessionId = logRevokeAdminJourneyBegin(userId);
+        mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_REVOKE_ADMIN);
         UserData user;
         synchronized (mPackagesLock) {
             synchronized (mUsersLock) {
                 user = getUserDataLU(userId);
-                if (user == null || !user.info.isAdmin()) {
-                    // Exit if no user found with that id, or the user is not an Admin.
-                    logUserJourneyError(sessionId, FrameworkStatsLog
-                                    .USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN,
-                            userId);
+                if (user == null) {
+                    // Exit if no user found with that id
+                    mUserJourneyLogger.logNullUserJourneyError(
+                            USER_JOURNEY_REVOKE_ADMIN,
+                            getCurrentUserId(), userId, "", -1);
+                    return;
+                } else if (!user.info.isAdmin()) {
+                    // Exit if no user is not an Admin.
+                    mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), user.info,
+                            USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_USER_IS_NOT_AN_ADMIN);
                     return;
                 }
                 user.info.flags ^= UserInfo.FLAG_ADMIN;
                 writeUserLP(user);
             }
         }
-        logRevokeAdminJourneyFinish(sessionId, userId, user.info.userType, user.info.flags);
+        mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), user.info,
+                USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_UNSPECIFIED);
     }
 
     /**
@@ -4700,16 +4721,20 @@
         final int noneUserId = -1;
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
         t.traceBegin("createUser-" + flags);
-        final long sessionId = logUserCreateJourneyBegin(noneUserId);
+        mUserJourneyLogger.logUserJourneyBegin(noneUserId, USER_JOURNEY_USER_CREATE);
         UserInfo newUser = null;
         try {
             newUser = createUserInternalUncheckedNoTracing(name, userType, flags, parentId,
                         preCreate, disallowedPackages, t, token);
             return newUser;
         } finally {
-            logUserCreateJourneyFinish(sessionId,
-                    newUser != null ? newUser.id : noneUserId, userType, flags,
-                    newUser != null);
+            if (newUser != null) {
+                mUserJourneyLogger.logUserCreateJourneyFinish(getCurrentUserId(), newUser);
+            } else {
+                mUserJourneyLogger.logNullUserJourneyError(
+                        USER_JOURNEY_USER_CREATE,
+                        getCurrentUserId(), noneUserId, userType, flags);
+            }
             t.traceEnd();
         }
     }
@@ -5198,137 +5223,6 @@
                 && !userTypeDetails.getName().equals(UserManager.USER_TYPE_FULL_RESTRICTED);
     }
 
-    private long logUserCreateJourneyBegin(@UserIdInt int userId) {
-        return logUserJourneyBegin(
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE,
-                userId);
-    }
-
-    private void logUserCreateJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
-            @UserInfoFlag int flags, boolean finish) {
-        logUserJourneyFinish(sessionId,
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE,
-                userId, userType, flags, finish);
-    }
-
-    private long logUserRemoveJourneyBegin(@UserIdInt int userId) {
-        return logUserJourneyBegin(
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE,
-                userId);
-    }
-
-    private void logUserRemoveJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
-            @UserInfoFlag int flags, boolean finish) {
-        logUserJourneyFinish(sessionId,
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE,
-                userId, userType, flags, finish);
-    }
-
-    private long logGrantAdminJourneyBegin(@UserIdInt int userId) {
-        return logUserJourneyBegin(
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN,
-                userId);
-    }
-
-    private void logGrantAdminJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
-            @UserInfoFlag int flags) {
-        logUserJourneyFinish(sessionId,
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN,
-                userId, userType, flags, true);
-    }
-
-    private long logRevokeAdminJourneyBegin(@UserIdInt int userId) {
-        return logUserJourneyBegin(
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN,
-                userId);
-    }
-
-    private void logRevokeAdminJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
-            @UserInfoFlag int flags) {
-        logUserJourneyFinish(sessionId,
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN,
-                userId, userType, flags, true);
-    }
-
-    private void logUserJourneyFinish(long sessionId, int journey, @UserIdInt int userId,
-            String userType, @UserInfoFlag int flags, boolean finish) {
-
-        // log the journey atom with the user metadata
-        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, sessionId,
-                journey, /* origin_user= */ getCurrentUserId(), userId,
-                UserManager.getUserTypeForStatsd(userType), flags);
-
-        int event;
-        switch (journey) {
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
-                break;
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REMOVE_USER;
-                break;
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
-                break;
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
-                break;
-            default:
-                throw new IllegalArgumentException("Journey " + journey + " not expected.");
-        }
-        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
-                event,
-                finish ? FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH
-                        : FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE);
-    }
-
-    private long logUserJourneyBegin(int journey, @UserIdInt int userId) {
-        final long sessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
-
-        // log the event atom to indicate the event start
-        int event;
-        switch (journey) {
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
-                break;
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REMOVE_USER;
-                break;
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
-                break;
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
-                break;
-            default:
-                throw new IllegalArgumentException("Journey " + journey + " not expected.");
-        }
-
-        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
-                event, FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN);
-        return sessionId;
-    }
-
-    private void logUserJourneyError(long sessionId, int journey, @UserIdInt int userId) {
-
-        // log the journey atom with the user metadata
-        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, sessionId,
-                journey, /* origin_user= */ getCurrentUserId(), userId);
-
-        int event;
-        switch (journey) {
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
-                break;
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
-                break;
-            default:
-                throw new IllegalArgumentException("Journey " + journey + " not expected.");
-        }
-        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
-                event, FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__ERROR);
-    }
-
     /** Register callbacks for statsd pulled atoms. */
     private void registerStatsCallbacks() {
         final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
@@ -5352,7 +5246,8 @@
             if (size > 1) {
                 for (int idx = 0; idx < size; idx++) {
                     final UserInfo user = users.get(idx);
-                    final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType);
+                    final int userTypeStandard = mUserJourneyLogger
+                            .getUserTypeForStatsd(user.userType);
                     final String userTypeCustom = (userTypeStandard == FrameworkStatsLog
                             .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN)
                             ?
@@ -5635,7 +5530,7 @@
                 writeUserLP(userData);
             }
 
-            final long sessionId = logUserRemoveJourneyBegin(userId);
+            mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_USER_REMOVE);
 
             try {
                 mAppOpsService.removeUser(userId);
@@ -5657,13 +5552,17 @@
                             @Override
                             public void userStopped(int userIdParam) {
                                 finishRemoveUser(userIdParam);
-                                logUserRemoveJourneyFinish(sessionId, userIdParam,
-                                        userData.info.userType, userData.info.flags, true);
+                                int originUserId = UserManagerService.this.getCurrentUserId();
+                                mUserJourneyLogger.logUserJourneyFinishWithError(originUserId,
+                                        userData.info, USER_JOURNEY_USER_REMOVE,
+                                        ERROR_CODE_UNSPECIFIED);
                             }
                             @Override
                             public void userStopAborted(int userIdParam) {
-                                logUserRemoveJourneyFinish(sessionId, userIdParam,
-                                        userData.info.userType, userData.info.flags, false);
+                                int originUserId = UserManagerService.this.getCurrentUserId();
+                                mUserJourneyLogger.logUserJourneyFinishWithError(originUserId,
+                                        userData.info, USER_JOURNEY_USER_REMOVE,
+                                        ERROR_CODE_ABORTED);
                             }
                         });
             } catch (RemoteException e) {
@@ -7297,9 +7196,9 @@
                 final UserInfo userInfo = getUserInfo(userIds[i]);
                 if (userInfo == null) {
                     // Not possible because the input user ids should all be valid
-                    userTypes[i] = UserManager.getUserTypeForStatsd("");
+                    userTypes[i] = mUserJourneyLogger.getUserTypeForStatsd("");
                 } else {
-                    userTypes[i] = UserManager.getUserTypeForStatsd(userInfo.userType);
+                    userTypes[i] = mUserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
                 }
             }
             return userTypes;
@@ -7536,4 +7435,11 @@
                 .getBoolean(R.bool.config_canSwitchToHeadlessSystemUser);
     }
 
+    /**
+     * Returns instance of {@link com.android.server.pm.UserJourneyLogger}.
+     */
+    public UserJourneyLogger getUserJourneyLogger() {
+        return mUserJourneyLogger;
+    }
+
 }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 9ff98be..f8954b7 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -5690,8 +5690,14 @@
             }
 
             if (eventTime > now) {
-                Slog.e(TAG, "Event time " + eventTime + " cannot be newer than " + now);
-                throw new IllegalArgumentException("event time must not be in the future");
+                Slog.wtf(TAG, "Event cannot be newer than the current time ("
+                        + "now=" + now
+                        + ", eventTime=" + eventTime
+                        + ", displayId=" + displayId
+                        + ", event=" + PowerManager.userActivityEventToString(event)
+                        + ", flags=" + flags
+                        + ")");
+                return;
             }
 
             final int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 24a271f..26b40b4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -582,6 +582,9 @@
 
     boolean mPauseSchedulePendingForPip = false;
 
+    // Gets set to indicate that the activity is currently being auto-pipped.
+    boolean mAutoEnteringPip = false;
+
     private void updateEnterpriseThumbnailDrawable(Context context) {
         DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
         mEnterpriseThumbnailDrawable = dpm.getResources().getDrawable(
@@ -6099,8 +6102,7 @@
             try {
                 mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
                         PauseActivityItem.obtain(finishing, false /* userLeaving */,
-                                configChangeFlags, false /* dontReport */,
-                                false /* autoEnteringPip */));
+                                configChangeFlags, false /* dontReport */, mAutoEnteringPip));
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
             }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a0ea1c3..b816dad 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3592,15 +3592,21 @@
         }
     }
 
+    boolean enterPictureInPictureMode(@NonNull ActivityRecord r,
+            @NonNull PictureInPictureParams params, boolean fromClient) {
+        return enterPictureInPictureMode(r, params, fromClient, false /* isAutoEnter */);
+    }
+
     /**
      * Puts the given activity in picture in picture mode if possible.
      *
      * @param fromClient true if this comes from a client call (eg. Activity.enterPip).
+     * @param isAutoEnter true if this comes from an automatic pip-enter.
      * @return true if the activity is now in picture-in-picture mode, or false if it could not
      * enter picture-in-picture mode.
      */
     boolean enterPictureInPictureMode(@NonNull ActivityRecord r,
-            @NonNull PictureInPictureParams params, boolean fromClient) {
+            @NonNull PictureInPictureParams params, boolean fromClient, boolean isAutoEnter) {
         // If the activity is already in picture in picture mode, then just return early
         if (r.inPinnedWindowingMode()) {
             return true;
@@ -3635,6 +3641,7 @@
                     return;
                 }
                 r.setPictureInPictureParams(params);
+                r.mAutoEnteringPip = isAutoEnter;
                 mRootWindowContainer.moveActivityToPinnedRootTask(r,
                         null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
                         transition);
@@ -3643,6 +3650,7 @@
                     r.getTask().schedulePauseActivity(r, false /* userLeaving */,
                             false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip");
                 }
+                r.mAutoEnteringPip = false;
             }
         };
 
@@ -6328,6 +6336,8 @@
         public void cleanupDisabledPackageComponents(
                 String packageName, Set<String> disabledClasses, int userId, boolean booted) {
             synchronized (mGlobalLock) {
+                // In case if setWindowManager hasn't been called yet when booting.
+                if (mRootWindowContainer == null) return;
                 // Clean-up disabled activities.
                 if (mRootWindowContainer.finishDisabledPackageActivities(
                         packageName, disabledClasses, true /* doit */, false /* evenPersistent */,
diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index 2edb082..7852249 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -20,41 +20,73 @@
 
 import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
 import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
+import static com.android.server.wm.DeviceStateController.DeviceState.FOLDED;
+import static com.android.server.wm.DeviceStateController.DeviceState.HALF_FOLDED;
+import static com.android.server.wm.DeviceStateController.DeviceState.OPEN;
 
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.graphics.Rect;
 import android.window.DisplayAreaInfo;
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.DeviceStateController.DeviceState;
+
 public class PhysicalDisplaySwitchTransitionLauncher {
 
     private final DisplayContent mDisplayContent;
-    private final WindowManagerService mService;
+    private final ActivityTaskManagerService mAtmService;
+    private final Context mContext;
     private final TransitionController mTransitionController;
 
     /**
-     * If on a foldable device represents whether the device is folded or not
+     * If on a foldable device represents whether we need to show unfold animation when receiving
+     * a physical display switch event
      */
-    private boolean mIsFolded;
+    private boolean mShouldRequestTransitionOnDisplaySwitch = false;
+    /**
+     * Current device state from {@link android.hardware.devicestate.DeviceStateManager}
+     */
+    private DeviceState mDeviceState = DeviceState.UNKNOWN;
     private Transition mTransition;
 
     public PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent,
             TransitionController transitionController) {
+        this(displayContent, displayContent.mWmService.mAtmService,
+                displayContent.mWmService.mContext, transitionController);
+    }
+
+    @VisibleForTesting
+    public PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent,
+            ActivityTaskManagerService service, Context context,
+            TransitionController transitionController) {
         mDisplayContent = displayContent;
-        mService = displayContent.mWmService;
+        mAtmService = service;
+        mContext = context;
         mTransitionController = transitionController;
     }
 
     /**
      *   Called by the DeviceStateManager callback when the state changes.
      */
-    void foldStateChanged(DeviceStateController.DeviceState newDeviceState) {
-        // Ignore transitions to/from half-folded.
-        if (newDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) return;
-        mIsFolded = newDeviceState == DeviceStateController.DeviceState.FOLDED;
+    void foldStateChanged(DeviceState newDeviceState) {
+        boolean isUnfolding = mDeviceState == FOLDED
+                && (newDeviceState == HALF_FOLDED || newDeviceState == OPEN);
+
+        if (isUnfolding) {
+            // Request transition only if we are unfolding the device
+            mShouldRequestTransitionOnDisplaySwitch = true;
+        } else if (newDeviceState != HALF_FOLDED && newDeviceState != OPEN) {
+            // Cancel the transition request in case if we are folding or switching to back
+            // to the rear display before the displays got switched
+            mShouldRequestTransitionOnDisplaySwitch = false;
+        }
+
+        mDeviceState = newDeviceState;
     }
 
     /**
@@ -62,12 +94,12 @@
      */
     public void requestDisplaySwitchTransitionIfNeeded(int displayId, int oldDisplayWidth,
             int oldDisplayHeight, int newDisplayWidth, int newDisplayHeight) {
+        if (!mShouldRequestTransitionOnDisplaySwitch) return;
         if (!mTransitionController.isShellTransitionsEnabled()) return;
         if (!mDisplayContent.getLastHasContent()) return;
 
-        boolean shouldRequestUnfoldTransition = !mIsFolded
-                && mService.mContext.getResources().getBoolean(config_unfoldTransitionEnabled)
-                && ValueAnimator.areAnimatorsEnabled();
+        boolean shouldRequestUnfoldTransition = mContext.getResources()
+                .getBoolean(config_unfoldTransitionEnabled) && ValueAnimator.areAnimatorsEnabled();
 
         if (!shouldRequestUnfoldTransition) {
             return;
@@ -91,6 +123,8 @@
             mDisplayContent.mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
             mTransition = t;
         }
+
+        mShouldRequestTransitionOnDisplaySwitch = false;
     }
 
     /**
@@ -118,7 +152,7 @@
         if (mTransition == null) return;
 
         if (transaction != null) {
-            mService.mAtmService.mWindowOrganizerController.applyTransaction(transaction);
+            mAtmService.mWindowOrganizerController.applyTransaction(transaction);
         }
 
         markTransitionAsReady();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 80f918b..50bf38b 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -963,7 +963,7 @@
                 return false;
             }
             return mController.mAtm.enterPictureInPictureMode(ar, ar.pictureInPictureArgs,
-                    false /* fromClient */);
+                    false /* fromClient */, true /* isAutoEnter */);
         }
 
         // Legacy pip-entry (not via isAutoEnterEnabled).
@@ -2267,7 +2267,7 @@
                         // Display won't be rotated for multi window Task, so the fixed rotation
                         // won't be applied. This can happen when the windowing mode is changed
                         // before the previous fixed rotation is applied.
-                        && !task.inMultiWindowMode()) {
+                        && (!task.inMultiWindowMode() || !topRunningActivity.inMultiWindowMode())) {
                     // If Activity is in fixed rotation, its will be applied with the next rotation,
                     // when the Task is still in the previous rotation.
                     final int taskRotation = task.getWindowConfiguration().getDisplayRotation();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f9b6fc1..40c6c46 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3915,8 +3915,9 @@
         synchronized (mGlobalLock) {
             final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
             if (displayContent == null) {
-                throw new IllegalStateException("No touch mode is defined for displayId {"
-                        + displayId + "}");
+                throw new IllegalStateException("Failed to retrieve the touch mode state for"
+                        + "display {" + displayId + "}: display is not registered in "
+                        + "WindowRootContainer");
             }
             return displayContent.isInTouchMode();
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index cf49dcf..0c4830a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -684,6 +684,38 @@
         }
     }
 
+    <V> void transferPolicies(EnforcingAdmin oldAdmin, EnforcingAdmin newAdmin) {
+        Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
+        for (PolicyKey policy : globalPolicies) {
+            PolicyState<?> policyState = mGlobalPolicies.get(policy);
+            if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) {
+                PolicyDefinition<V> policyDefinition =
+                        (PolicyDefinition<V>) policyState.getPolicyDefinition();
+                PolicyValue<V> policyValue =
+                        (PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin);
+                setGlobalPolicy(policyDefinition, newAdmin, policyValue);
+            }
+        }
+
+        for (int i = 0; i < mLocalPolicies.size(); i++) {
+            int userId = mLocalPolicies.keyAt(i);
+            Set<PolicyKey> localPolicies = new HashSet<>(
+                    mLocalPolicies.get(userId).keySet());
+            for (PolicyKey policy : localPolicies) {
+                PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
+                if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) {
+                    PolicyDefinition<V> policyDefinition =
+                            (PolicyDefinition<V>) policyState.getPolicyDefinition();
+                    PolicyValue<V> policyValue =
+                            (PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin);
+                    setLocalPolicy(policyDefinition, newAdmin, policyValue, userId);
+                }
+            }
+        }
+
+        removePoliciesForAdmin(oldAdmin);
+    }
+
     private Set<UserRestrictionPolicyKey> getUserRestrictionPolicyKeysForAdminLocked(
             Map<PolicyKey, PolicyState<?>> policies,
             EnforcingAdmin admin) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index dedc073..3578b16 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3882,6 +3882,17 @@
         final ActiveAdmin adminToTransfer = policy.mAdminMap.get(outgoingReceiver);
         final int oldAdminUid = adminToTransfer.getUid();
 
+        if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
+            EnforcingAdmin oldAdmin =
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                            outgoingReceiver, userHandle, adminToTransfer);
+            EnforcingAdmin newAdmin =
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                            incomingReceiver, userHandle, adminToTransfer);
+
+            mDevicePolicyEngine.transferPolicies(oldAdmin, newAdmin);
+        }
+
         adminToTransfer.transfer(incomingDeviceInfo);
         policy.mAdminMap.remove(outgoingReceiver);
         policy.mAdminMap.put(incomingReceiver, adminToTransfer);
@@ -6051,7 +6062,7 @@
     @Override
     public void lockNow(int flags, String callerPackageName, boolean parent) {
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(callerPackageName);
         } else {
             caller = getCallerIdentity();
@@ -6063,7 +6074,7 @@
             ActiveAdmin admin;
             // Make sure the caller has any active admin with the right policy or
             // the required permission.
-            if (isPermissionCheckFlagEnabled()) {
+            if (isUnicornFlagEnabled()) {
                 admin = enforcePermissionAndGetEnforcingAdmin(
                         /* admin= */ null,
                         /* permission= */ MANAGE_DEVICE_POLICY_LOCK,
@@ -8917,13 +8928,13 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             // The effect of this policy is device-wide.
             enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
         } else {
@@ -8951,13 +8962,13 @@
             return false;
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             enforceCanQuery(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
         } else {
             Objects.requireNonNull(who, "ComponentName is null");
@@ -8980,13 +8991,13 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             // The effect of this policy is device-wide.
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     who,
@@ -9026,13 +9037,13 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             // The effect of this policy is device-wide.
             enforceCanQuery(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL);
         } else {
@@ -9335,7 +9346,7 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
@@ -9345,7 +9356,7 @@
         final int userHandle = caller.getUserId();
         int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
         synchronized (getLockObject()) {
-            if (isPermissionCheckFlagEnabled()) {
+            if (isUnicornFlagEnabled()) {
                 // SUPPORT USES_POLICY_DISABLE_KEYGUARD_FEATURES
                 EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
                         who, MANAGE_DEVICE_POLICY_KEYGUARD, caller.getPackageName(),
@@ -9424,13 +9435,14 @@
 
         synchronized (getLockObject()) {
             if (who != null) {
-                if (isPermissionCheckFlagEnabled()) {
-                    EnforcingAdmin admin = getEnforcingAdminForCaller(
-                            who, who.getPackageName());
+                if (isUnicornFlagEnabled()) {
+                    EnforcingAdmin admin = getEnforcingAdminForPackage(
+                            who, who.getPackageName(), userHandle);
                     Integer features = mDevicePolicyEngine.getLocalPolicySetByAdmin(
                             PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
                             admin,
                             affectedUserId);
+
                     return features == null ? 0 : features;
                 } else {
                     ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
@@ -9438,7 +9450,7 @@
                 }
             }
 
-            if (isPermissionCheckFlagEnabled()) {
+            if (isUnicornFlagEnabled()) {
                 Integer features = mDevicePolicyEngine.getResolvedPolicy(
                         PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
                         affectedUserId);
@@ -9999,10 +10011,6 @@
                         "clearDeviceOwner can only be called by the device owner");
             }
             enforceUserUnlocked(deviceOwnerUserId);
-            DevicePolicyData policy = getUserData(deviceOwnerUserId);
-            if (policy.mPasswordTokenHandle != 0) {
-                mLockPatternUtils.removeEscrowToken(policy.mPasswordTokenHandle, deviceOwnerUserId);
-            }
 
             final ActiveAdmin admin = getDeviceOwnerAdminLocked();
             mInjector.binderWithCleanCallingIdentity(() -> {
@@ -10057,6 +10065,10 @@
         }
         final DevicePolicyData policyData = getUserData(userId);
         policyData.mCurrentInputMethodSet = false;
+        if (policyData.mPasswordTokenHandle != 0) {
+            mLockPatternUtils.removeEscrowToken(policyData.mPasswordTokenHandle, userId);
+            policyData.mPasswordTokenHandle = 0;
+        }
         saveSettingsLocked(userId);
         mPolicyCache.onUserRemoved(userId);
         final DevicePolicyData systemPolicyData = getUserData(UserHandle.USER_SYSTEM);
@@ -11637,7 +11649,7 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS);
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     who,
                     MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
@@ -13064,7 +13076,7 @@
             String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
                     who,
                     MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
@@ -13134,7 +13146,7 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         ActiveAdmin admin;
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     who,
                     MANAGE_DEVICE_POLICY_PACKAGE_STATE,
@@ -13231,7 +13243,7 @@
     public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             enforcePermission(
                     MANAGE_DEVICE_POLICY_PACKAGE_STATE,
                     caller.getPackageName(),
@@ -13837,7 +13849,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 {
@@ -13856,7 +13868,7 @@
         boolean result;
         synchronized (getLockObject()) {
             if (parent) {
-                if (!isPermissionCheckFlagEnabled()) {
+                if (!isPolicyEngineForFinanceFlagEnabled()) {
                     Preconditions.checkCallAuthorization(
                             isProfileOwnerOfOrganizationOwnedDevice(
                                     caller.getUserId()) && isManagedProfile(caller.getUserId()));
@@ -13873,7 +13885,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),
@@ -13912,7 +13924,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 {
@@ -13924,7 +13936,7 @@
 
         synchronized (getLockObject()) {
             if (parent) {
-                if (!isPermissionCheckFlagEnabled()) {
+                if (!isPolicyEngineForFinanceFlagEnabled()) {
                     Preconditions.checkCallAuthorization(
                             isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId())
                                     && isManagedProfile(caller.getUserId()));
@@ -14116,13 +14128,13 @@
         enforceMaxStringLength(accountType, "account type");
 
         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,
@@ -14185,7 +14197,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,
@@ -15556,12 +15568,12 @@
     public boolean setStatusBarDisabled(ComponentName who, String callerPackageName,
             boolean disabled) {
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             enforcePermission(MANAGE_DEVICE_POLICY_STATUS_BAR, caller.getPackageName(),
                     UserHandle.USER_ALL);
         } else {
@@ -15572,7 +15584,7 @@
 
         int userId = caller.getUserId();
         synchronized (getLockObject()) {
-            if (!isPermissionCheckFlagEnabled()) {
+            if (!isUnicornFlagEnabled()) {
                 Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId),
                         "Admin " + who + " is neither the device owner or affiliated "
                                 + "user's profile owner.");
@@ -15631,7 +15643,7 @@
     @Override
     public boolean isStatusBarDisabled(String callerPackage) {
         final CallerIdentity caller = getCallerIdentity(callerPackage);
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             enforceCanQuery(
                     MANAGE_DEVICE_POLICY_STATUS_BAR, caller.getPackageName(), caller.getUserId());
         } else {
@@ -15641,7 +15653,7 @@
 
         int userId = caller.getUserId();
         synchronized (getLockObject()) {
-            if (!isPermissionCheckFlagEnabled()) {
+            if (!isUnicornFlagEnabled()) {
                 Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId),
                         "Admin " + callerPackage
                                 + " is neither the device owner or affiliated user's profile owner.");
@@ -16802,7 +16814,7 @@
             }
         }
         EnforcingAdmin enforcingAdmin;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
@@ -16973,7 +16985,7 @@
     public int getPermissionGrantState(ComponentName admin, String callerPackage,
             String packageName, String permission) throws RemoteException {
         final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             enforceCanQuery(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, caller.getPackageName(),
                     caller.getUserId());
         } else {
@@ -19111,14 +19123,14 @@
             throw new IllegalArgumentException("token must be at least 32-byte long");
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(admin, callerPackageName);
         } else {
             caller = getCallerIdentity(admin);
         }
         final int userId = caller.getUserId();
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19174,7 +19186,7 @@
             return false;
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(admin, callerPackageName);
         } else {
             caller = getCallerIdentity(admin);
@@ -19182,7 +19194,7 @@
         final int userId = caller.getUserId();
         boolean result = false;
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19221,14 +19233,14 @@
             return false;
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(admin, callerPackageName);
         } else {
             caller = getCallerIdentity(admin);
         }
         int userId = caller.getUserId();
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19270,7 +19282,7 @@
         Objects.requireNonNull(token);
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(admin, callerPackageName);
         } else {
             caller = getCallerIdentity(admin);
@@ -19280,7 +19292,7 @@
         boolean result = false;
         final String password = passwordOrNull != null ? passwordOrNull : "";
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19311,7 +19323,7 @@
         }
 
         if (result) {
-            if (isPermissionCheckFlagEnabled()) {
+            if (isUnicornFlagEnabled()) {
                 DevicePolicyEventLogger
                         .createEvent(DevicePolicyEnums.RESET_PASSWORD_WITH_TOKEN)
                         .setAdmin(callerPackageName)
@@ -23569,6 +23581,30 @@
         return  EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId, admin);
     }
 
+    private EnforcingAdmin getEnforcingAdminForPackage(@Nullable ComponentName who,
+            String packageName, int userId) {
+        ActiveAdmin admin;
+        if (who != null) {
+            if (isDeviceOwner(who, userId) || isProfileOwner(who, userId)) {
+                synchronized (getLockObject()) {
+                    admin = getActiveAdminUncheckedLocked(who, userId);
+                }
+                if (admin != null) {
+                    return EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId, admin);
+                }
+            } else {
+                // Check for non-DPC active admins.
+                admin = getActiveAdminUncheckedLocked(who, userId);
+                if (admin != null) {
+                    return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId, admin);
+                }
+            }
+        }
+
+        admin = getUserData(userId).createOrGetPermissionBasedAdmin(userId);
+        return  EnforcingAdmin.createEnforcingAdmin(packageName, userId, admin);
+    }
+
     private int getAffectedUser(boolean calledOnParent) {
         int callingUserId = mInjector.userHandleGetCallingUserId();
         return calledOnParent ? getProfileParentId(callingUserId) : callingUserId;
@@ -23624,6 +23660,10 @@
                 DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
     }
 
+    private boolean isUnicornFlagEnabled() {
+        return false;
+    }
+
     private boolean isWorkProfileTelephonyEnabled() {
         return isWorkProfileTelephonyDevicePolicyManagerFlagEnabled()
                 && isWorkProfileTelephonySubscriptionManagerFlagEnabled();
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
index 941a3a4..3faf394 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -72,18 +72,28 @@
 
         mResources = InstrumentationRegistry.getContext().getResources();
         // These Resources are common to all tests.
-        doReturn(mResources.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin))
+        doReturn(4000)
             .when(mMockedResources)
             .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin);
-        doReturn(mResources.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax))
+        doReturn(8000)
             .when(mMockedResources)
             .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax);
-        doReturn(mResources.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault))
+        doReturn(6500)
             .when(mMockedResources)
             .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
-        doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite))
-            .when(mMockedResources)
-            .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
+        doReturn(new String[] {"0.950456", "1.000000", "1.089058"})
+                .when(mMockedResources)
+                .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
+        doReturn(6500)
+                .when(mMockedResources)
+                .getInteger(R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
+        doReturn(new int[] {0})
+                .when(mMockedResources)
+                .getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
+        doReturn(new int[] {20})
+                .when(mMockedResources)
+                .getIntArray(R.array.config_displayWhiteBalanceDisplayRangeMinimums);
+
         doReturn(mMockedResources).when(mMockedContext).getResources();
 
         mDisplayToken = new Binder();
@@ -195,7 +205,7 @@
      * Matrix should match the precalculated one for given cct and display primaries.
      */
     @Test
-    public void displayWhiteBalance_validateTransformMatrix() {
+    public void displayWhiteBalance_getAndSetMatrix_validateTransformMatrix() {
         DisplayPrimaries displayPrimaries = new DisplayPrimaries();
         displayPrimaries.red = new CieXyz();
         displayPrimaries.red.X = 0.412315f;
@@ -223,10 +233,12 @@
 
         final int cct = 6500;
         mDisplayWhiteBalanceTintController.setMatrix(cct);
+        mDisplayWhiteBalanceTintController.setAppliedCct(
+                mDisplayWhiteBalanceTintController.getTargetCct());
+
         assertWithMessage("Failed to set temperature")
                 .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
                 .isEqualTo(cct);
-
         float[] matrixDwb = mDisplayWhiteBalanceTintController.getMatrix();
         final float[] expectedMatrixDwb = {
             0.971848f,   -0.001421f,  0.000491f, 0.0f,
@@ -238,6 +250,54 @@
             1e-6f /* tolerance */);
     }
 
+    /**
+     * Matrix should match the precalculated one for given cct and display primaries.
+     */
+    @Test
+    public void displayWhiteBalance_targetApplied_validateTransformMatrix() {
+        DisplayPrimaries displayPrimaries = new DisplayPrimaries();
+        displayPrimaries.red = new CieXyz();
+        displayPrimaries.red.X = 0.412315f;
+        displayPrimaries.red.Y = 0.212600f;
+        displayPrimaries.red.Z = 0.019327f;
+        displayPrimaries.green = new CieXyz();
+        displayPrimaries.green.X = 0.357600f;
+        displayPrimaries.green.Y = 0.715200f;
+        displayPrimaries.green.Z = 0.119200f;
+        displayPrimaries.blue = new CieXyz();
+        displayPrimaries.blue.X = 0.180500f;
+        displayPrimaries.blue.Y = 0.072200f;
+        displayPrimaries.blue.Z = 0.950633f;
+        displayPrimaries.white = new CieXyz();
+        displayPrimaries.white.X = 0.950456f;
+        displayPrimaries.white.Y = 1.000000f;
+        displayPrimaries.white.Z = 1.089058f;
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
+
+        setUpTintController();
+        assertWithMessage("Setup with valid SurfaceControl failed")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isTrue();
+
+        final int cct = 6500;
+        mDisplayWhiteBalanceTintController.setTargetCct(cct);
+        final float[] matrixDwb = mDisplayWhiteBalanceTintController.computeMatrixForCct(cct);
+        mDisplayWhiteBalanceTintController.setAppliedCct(cct);
+
+        assertWithMessage("Failed to set temperature")
+                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+                .isEqualTo(cct);
+        final float[] expectedMatrixDwb = {
+                0.971848f,   -0.001421f,  0.000491f, 0.0f,
+                0.028193f,    0.945798f,  0.003207f, 0.0f,
+                -0.000042f,  -0.000989f,  0.988659f, 0.0f,
+                0.0f,         0.0f,       0.0f,      1.0f
+        };
+        assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+                1e-6f /* tolerance */);
+    }
+
     private void setUpTintController() {
         mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
                 mDisplayManagerInternal);
diff --git a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java
index f788c92..46974cf7 100644
--- a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java
@@ -28,6 +28,8 @@
 import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
@@ -81,7 +83,7 @@
     public void testEnqueueUidChange() {
         int change = mUidObserverController.enqueueUidChange(null, TEST_UID1,
                 UidRecord.CHANGE_ACTIVE, PROCESS_STATE_FOREGROUND_SERVICE,
-                PROCESS_CAPABILITY_ALL, 0, false);
+                UNKNOWN_ADJ, PROCESS_CAPABILITY_ALL, 0, false);
         assertEquals("expected=ACTIVE,actual=" + changeToStr(change),
                 UidRecord.CHANGE_ACTIVE, change);
         assertPendingChange(TEST_UID1, UidRecord.CHANGE_ACTIVE, PROCESS_STATE_FOREGROUND_SERVICE,
@@ -91,8 +93,8 @@
 
         final ChangeRecord record2 = new ChangeRecord();
         change = mUidObserverController.enqueueUidChange(record2, TEST_UID2,
-                UidRecord.CHANGE_CACHED, PROCESS_STATE_CACHED_RECENT, PROCESS_CAPABILITY_NONE,
-                99, true);
+                UidRecord.CHANGE_CACHED, PROCESS_STATE_CACHED_RECENT, UNKNOWN_ADJ,
+                PROCESS_CAPABILITY_NONE, 99, true);
         assertEquals("expected=ACTIVE,actual=" + changeToStr(change),
                 UidRecord.CHANGE_CACHED, change);
         assertPendingChange(TEST_UID1, UidRecord.CHANGE_ACTIVE, PROCESS_STATE_FOREGROUND_SERVICE,
@@ -101,7 +103,8 @@
                 PROCESS_CAPABILITY_NONE, 99, true, record2);
 
         change = mUidObserverController.enqueueUidChange(record1, TEST_UID1,
-                UidRecord.CHANGE_UNCACHED, PROCESS_STATE_TOP, PROCESS_CAPABILITY_ALL, 0, false);
+                UidRecord.CHANGE_UNCACHED, PROCESS_STATE_TOP, UNKNOWN_ADJ,
+                PROCESS_CAPABILITY_ALL, 0, false);
         assertEquals("expected=ACTIVE|UNCACHED,actual=" + changeToStr(change),
                 UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_UNCACHED, change);
         assertPendingChange(TEST_UID1, UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_UNCACHED,
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index d12741a..317fd58 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -100,6 +100,7 @@
 import com.android.server.FgThread;
 import com.android.server.SystemService;
 import com.android.server.am.UserState.KeyEvictedCallback;
+import com.android.server.pm.UserJourneyLogger;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
 import com.android.server.wm.WindowManagerService;
@@ -199,6 +200,7 @@
             mUserController.setAllowUserUnlocking(true);
             setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
             setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated= */ true, null);
+            mInjector.mRelevantUser = null;
         });
     }
 
@@ -232,6 +234,25 @@
     }
 
     @Test
+    public void testStartUser_background_duringBootHsum() {
+        mockIsHeadlessSystemUserMode(true);
+        mUserController.setAllowUserUnlocking(false);
+        mInjector.mRelevantUser = TEST_USER_ID;
+        boolean started = mUserController.startUser(TEST_USER_ID, USER_START_MODE_BACKGROUND);
+        assertWithMessage("startUser(%s, foreground=false)", TEST_USER_ID).that(started).isTrue();
+
+        // ACTION_LOCKED_BOOT_COMPLETED not sent yet
+        startUserAssertions(newArrayList(Intent.ACTION_USER_STARTED, Intent.ACTION_USER_STARTING),
+                START_BACKGROUND_USER_MESSAGE_CODES);
+
+        mUserController.onBootComplete(null);
+
+        startUserAssertions(newArrayList(Intent.ACTION_USER_STARTED, Intent.ACTION_USER_STARTING,
+                        Intent.ACTION_LOCKED_BOOT_COMPLETED),
+                START_BACKGROUND_USER_MESSAGE_CODES);
+    }
+
+    @Test
     public void testStartUser_sendsNoBroadcastsForSystemUserInNonHeadlessMode() {
         setUpUser(SYSTEM_USER_ID, UserInfo.FLAG_SYSTEM, /* preCreated= */ false,
                 UserManager.USER_TYPE_FULL_SYSTEM);
@@ -1074,8 +1095,12 @@
         private final KeyguardManager mKeyguardManagerMock;
         private final LockPatternUtils mLockPatternUtilsMock;
 
+        private final UserJourneyLogger mUserJourneyLoggerMock;
+
         private final Context mCtx;
 
+        private Integer mRelevantUser;
+
         TestInjector(Context ctx) {
             super(null);
             mCtx = ctx;
@@ -1090,6 +1115,7 @@
             mKeyguardManagerMock = mock(KeyguardManager.class);
             when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
             mLockPatternUtilsMock = mock(LockPatternUtils.class);
+            mUserJourneyLoggerMock = mock(UserJourneyLogger.class);
         }
 
         @Override
@@ -1162,7 +1188,9 @@
                 boolean sticky, int callingPid, int callingUid, int realCallingUid,
                 int realCallingPid, int userId) {
             Log.i(TAG, "broadcastIntentLocked " + intent);
-            mSentIntents.add(intent);
+            if (mRelevantUser == null || mRelevantUser == userId || userId == UserHandle.USER_ALL) {
+                mSentIntents.add(intent);
+            }
             return 0;
         }
 
@@ -1220,6 +1248,11 @@
         void onSystemUserVisibilityChanged(boolean visible) {
             Log.i(TAG, "onSystemUserVisibilityChanged(" + visible + ")");
         }
+
+        @Override
+        protected UserJourneyLogger getUserJourneyLogger() {
+            return mUserJourneyLoggerMock;
+        }
     }
 
     private static class TestHandler extends Handler {
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
index c5a9af7..dcd06c9 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
@@ -30,7 +30,7 @@
     @Test
     public void call_writeToParcel_fromParcel_reconstructsSuccessfully() {
         final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
-        final long id = 5;
+        final String id = "5";
         final String callerId = "callerId";
         final byte[] appIcon = "appIcon".getBytes();
         final String appName = "appName";
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallServiceTest.java
index a488ab4..afddf3c 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallServiceTest.java
@@ -47,24 +47,24 @@
 
     @Test
     public void getCallForId_invalid() {
-        when(mMockCrossDeviceCall.getId()).thenReturn(-1L);
-        final CrossDeviceCall call = mSyncInCallService.getCallForId(-1L,
+        when(mMockCrossDeviceCall.getId()).thenReturn(null);
+        final CrossDeviceCall call = mSyncInCallService.getCallForId(null,
                 List.of(mMockCrossDeviceCall));
         assertWithMessage("Unexpectedly found a match for call id").that(call).isNull();
     }
 
     @Test
     public void getCallForId_noMatch() {
-        when(mMockCrossDeviceCall.getId()).thenReturn(5L);
-        final CrossDeviceCall call = mSyncInCallService.getCallForId(1L,
+        when(mMockCrossDeviceCall.getId()).thenReturn("123abc");
+        final CrossDeviceCall call = mSyncInCallService.getCallForId("abc123",
                 List.of(mMockCrossDeviceCall));
         assertWithMessage("Unexpectedly found a match for call id").that(call).isNull();
     }
 
     @Test
     public void getCallForId_hasMatch() {
-        when(mMockCrossDeviceCall.getId()).thenReturn(5L);
-        final CrossDeviceCall call = mSyncInCallService.getCallForId(5L,
+        when(mMockCrossDeviceCall.getId()).thenReturn("123abc");
+        final CrossDeviceCall call = mSyncInCallService.getCallForId("123abc",
                 List.of(mMockCrossDeviceCall));
         assertWithMessage("Unexpectedly did not find a match for call id").that(call).isNotNull();
     }
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
index 9d42a5b..5a0646c 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
@@ -62,9 +62,9 @@
         assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.RINGING);
         assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.ACCEPT,
-                        android.companion.Telecom.Call.REJECT,
-                        android.companion.Telecom.Call.SILENCE));
+                .isEqualTo(Set.of(android.companion.Telecom.ACCEPT,
+                        android.companion.Telecom.REJECT,
+                        android.companion.Telecom.SILENCE));
     }
 
     @Test
@@ -77,9 +77,9 @@
         assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.ONGOING);
         assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.END,
-                        android.companion.Telecom.Call.MUTE,
-                        android.companion.Telecom.Call.PUT_ON_HOLD));
+                .isEqualTo(Set.of(android.companion.Telecom.END,
+                        android.companion.Telecom.MUTE,
+                        android.companion.Telecom.PUT_ON_HOLD));
     }
 
     @Test
@@ -92,8 +92,8 @@
         assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.ON_HOLD);
         assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.END,
-                        android.companion.Telecom.Call.TAKE_OFF_HOLD));
+                .isEqualTo(Set.of(android.companion.Telecom.END,
+                        android.companion.Telecom.TAKE_OFF_HOLD));
     }
 
     @Test
@@ -106,8 +106,8 @@
         assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.ONGOING);
         assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.END,
-                        android.companion.Telecom.Call.MUTE));
+                .isEqualTo(Set.of(android.companion.Telecom.END,
+                        android.companion.Telecom.MUTE));
     }
 
     @Test
@@ -120,8 +120,8 @@
         assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.ONGOING);
         assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.END,
-                        android.companion.Telecom.Call.PUT_ON_HOLD));
+                .isEqualTo(Set.of(android.companion.Telecom.END,
+                        android.companion.Telecom.PUT_ON_HOLD));
     }
 
     @Test
@@ -134,17 +134,17 @@
         assertWithMessage("Wrong status for ringing state").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.RINGING);
         assertWithMessage("Wrong controls for ringing state").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.ACCEPT,
-                        android.companion.Telecom.Call.REJECT,
-                        android.companion.Telecom.Call.SILENCE));
+                .isEqualTo(Set.of(android.companion.Telecom.ACCEPT,
+                        android.companion.Telecom.REJECT,
+                        android.companion.Telecom.SILENCE));
         crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_ACTIVE,
                 Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
         assertWithMessage("Wrong status for active state").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.ONGOING);
         assertWithMessage("Wrong controls for active state").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.END,
-                        android.companion.Telecom.Call.MUTE,
-                        android.companion.Telecom.Call.PUT_ON_HOLD));
+                .isEqualTo(Set.of(android.companion.Telecom.END,
+                        android.companion.Telecom.MUTE,
+                        android.companion.Telecom.PUT_ON_HOLD));
     }
 
     @Test
@@ -158,8 +158,8 @@
         assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.RINGING_SILENCED);
         assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.ACCEPT,
-                        android.companion.Telecom.Call.REJECT));
+                .isEqualTo(Set.of(android.companion.Telecom.ACCEPT,
+                        android.companion.Telecom.REJECT));
     }
 
     @Test
@@ -173,9 +173,9 @@
         assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.ONGOING);
         assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.END,
-                        android.companion.Telecom.Call.MUTE,
-                        android.companion.Telecom.Call.PUT_ON_HOLD));
+                .isEqualTo(Set.of(android.companion.Telecom.END,
+                        android.companion.Telecom.MUTE,
+                        android.companion.Telecom.PUT_ON_HOLD));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
index eec026cc..25b0ae4 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
@@ -81,7 +81,7 @@
 
     @Test
     public void processTelecomDataFromSync_createCallUpdateMessage_hasCalls() {
-        when(mMockCrossDeviceCall.getId()).thenReturn(5L);
+        when(mMockCrossDeviceCall.getId()).thenReturn("123abc");
         final String callerId = "Firstname Lastname";
         when(mMockCrossDeviceCall.getReadableCallerId(anyBoolean())).thenReturn(callerId);
         final String appName = "AppName";
@@ -90,9 +90,9 @@
         when(mMockCrossDeviceCall.getCallingAppIcon()).thenReturn(appIcon.getBytes());
         when(mMockCrossDeviceCall.getStatus()).thenReturn(android.companion.Telecom.Call.RINGING);
         final Set<Integer> controls = Set.of(
-                android.companion.Telecom.Call.ACCEPT,
-                android.companion.Telecom.Call.REJECT,
-                android.companion.Telecom.Call.SILENCE);
+                android.companion.Telecom.ACCEPT,
+                android.companion.Telecom.REJECT,
+                android.companion.Telecom.SILENCE);
         when(mMockCrossDeviceCall.getControls()).thenReturn(controls);
         final byte[] data = mCrossDeviceSyncController.createCallUpdateMessage(
                 new HashSet<>(List.of(mMockCrossDeviceCall)),
@@ -103,35 +103,33 @@
                 callMetadataSyncData.getCalls()).hasSize(1);
         final CallMetadataSyncData.Call call =
                 callMetadataSyncData.getCalls().stream().findAny().orElseThrow();
-        assertWithMessage("Wrong id").that(call.getId()).isEqualTo(5L);
+        assertWithMessage("Wrong id").that(call.getId()).isEqualTo("123abc");
         assertWithMessage("Wrong app icon").that(new String(call.getAppIcon())).isEqualTo(appIcon);
         assertWithMessage("Wrong app name").that(call.getAppName()).isEqualTo(appName);
         assertWithMessage("Wrong caller id").that(call.getCallerId()).isEqualTo(callerId);
         assertWithMessage("Wrong status").that(call.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.RINGING);
         assertWithMessage("Wrong controls").that(call.getControls()).isEqualTo(controls);
-        assertWithMessage("Unexpectedly has requests").that(
-                callMetadataSyncData.getRequests()).isEmpty();
     }
 
     @Test
     public void processTelecomDataFromMessage_createCallControlMessage_hasCallControlRequest() {
         final byte[] data = CrossDeviceSyncController.createCallControlMessage(
-                /* callId= */ 5L, /* status= */ android.companion.Telecom.Call.ACCEPT);
+                /* callId= */ "123abc", /* status= */ android.companion.Telecom.ACCEPT);
         final CallMetadataSyncData callMetadataSyncData =
                 mCrossDeviceSyncController.processTelecomDataFromSync(data);
         assertWithMessage("Wrong number of requests").that(
                 callMetadataSyncData.getRequests()).hasSize(1);
         final CallMetadataSyncData.Call call =
                 callMetadataSyncData.getRequests().stream().findAny().orElseThrow();
-        assertWithMessage("Wrong id").that(call.getId()).isEqualTo(5L);
+        assertWithMessage("Wrong id").that(call.getId()).isEqualTo("123abc");
         assertWithMessage("Wrong app icon").that(call.getAppIcon()).isNull();
         assertWithMessage("Wrong app name").that(call.getAppName()).isNull();
         assertWithMessage("Wrong caller id").that(call.getCallerId()).isNull();
         assertWithMessage("Wrong status").that(call.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.UNKNOWN_STATUS);
         assertWithMessage("Wrong controls").that(call.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.ACCEPT));
+                .isEqualTo(Set.of(android.companion.Telecom.ACCEPT));
         assertWithMessage("Unexpectedly has active calls").that(
                 callMetadataSyncData.getCalls()).isEmpty();
     }
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 1e342f5..57755a9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1512,6 +1512,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);
@@ -2602,6 +2603,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetApplicationHiddenWithDO() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -2627,6 +2629,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 =
@@ -4373,6 +4376,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledModifiesSetting() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -4384,6 +4388,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledWithPOOnUser0() throws Exception {
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         setupProfileOwnerOnUser0();
@@ -4395,6 +4400,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledFailWithPONotOnUser0() throws Exception {
         setupProfileOwner();
         assertExpectException(SecurityException.class, null,
@@ -4404,6 +4410,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledWithPOOfOrganizationOwnedDevice() throws Exception {
         setupProfileOwner();
         configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
@@ -5377,6 +5384,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testResetPasswordWithToken() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -5411,6 +5419,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void resetPasswordWithToken_NumericPin() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -5431,6 +5440,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void resetPasswordWithToken_EmptyPassword() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -7251,6 +7261,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testCanProfileOwnerResetPasswordWhenLocked() throws Exception {
         setDeviceEncryptionPerUser();
         setupProfileOwner();
@@ -7314,6 +7325,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAccountTypesWithManagementDisabledOnManagedProfile() throws Exception {
         setupProfileOwner();
 
@@ -7333,6 +7345,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/color/CctEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java
new file mode 100644
index 0000000..b96666a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.color;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CctEvaluatorTest {
+
+    @Test
+    public void noEntriesInParallelArrays_setsEverythingToOne() {
+        final CctEvaluator evaluator = new CctEvaluator(0, 5, new int[]{}, new int[]{});
+        assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{1, 1, 1, 1, 1, 1});
+        assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+                new int[]{0, 1, 2, 3, 4, 5});
+    }
+
+    @Test
+    public void unevenNumberOfEntriesInParallelArrays_setsEverythingToOne() {
+        final CctEvaluator evaluator = new CctEvaluator(0, 5, new int[]{0}, new int[]{});
+        assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{1, 1, 1, 1, 1, 1});
+        assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+                new int[]{0, 1, 2, 3, 4, 5});
+    }
+
+    @Test
+    public void singleEntryInParallelArray_computesCorrectly() {
+        final CctEvaluator evaluator = new CctEvaluator(0, 5, new int[]{0}, new int[]{2});
+        assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{2, 2, 2, 2, 2, 2});
+        assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+                new int[]{0, 0, 2, 2, 4, 4});
+    }
+
+    @Test
+    public void minimumIsBelowFirstRange_computesCorrectly() {
+        final CctEvaluator evaluator = new CctEvaluator(3000, 3005, new int[]{3002},
+                new int[]{20});
+        assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{20, 20, 20, 20, 20, 20});
+        assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+                new int[]{3000, 3000, 3000, 3000, 3000, 3000});
+    }
+
+    @Test
+    public void minimumIsAboveFirstRange_computesCorrectly() {
+        final CctEvaluator evaluator = new CctEvaluator(3000, 3008, new int[]{3002},
+                new int[]{20});
+        assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(
+                new int[]{20, 20, 20, 20, 20, 20, 20, 20, 20});
+        assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+                new int[]{3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000});
+    }
+
+    @Test
+    public void multipleStepsStartsAtThreshold_computesCorrectly() {
+        final CctEvaluator evaluator = new CctEvaluator(5, 20, new int[]{0, 4, 5, 10, 18},
+                new int[]{11, 7, 2, 15, 9});
+        assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(
+                new int[]{2, 2, 2, 2, 2, 15, 15, 15, 15, 15, 15, 15, 15, 9, 9, 9});
+        assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+                new int[]{5, 5, 7, 7, 9, 9, 9, 9, 9, 9, 9, 9, 9, 18, 18, 18});
+    }
+
+    @Test
+    public void multipleStepsStartsInBetween_computesCorrectly() {
+        final CctEvaluator evaluator = new CctEvaluator(4, 20, new int[]{0, 5, 10, 18},
+                new int[]{14, 2, 15, 9});
+        assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(
+                new int[]{14, 2, 2, 2, 2, 2, 15, 15, 15, 15, 15, 15, 15, 15, 9, 9, 9});
+        assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+                new int[]{4, 4, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 18, 18, 18});
+    }
+}
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 d0d28c3..55c45df 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -85,6 +85,9 @@
         const val DEVICE_ID = 1
         const val VENDOR_SPECIFIC_DEVICE_ID = 2
         const val ENGLISH_DVORAK_DEVICE_ID = 3
+        const val ENGLISH_QWERTY_DEVICE_ID = 4
+        const val DEFAULT_VENDOR_ID = 123
+        const val DEFAULT_PRODUCT_ID = 456
         const val USER_ID = 4
         const val IME_ID = "ime_id"
         const val PACKAGE_NAME = "KeyboardLayoutManagerTests"
@@ -122,6 +125,7 @@
     private lateinit var keyboardDevice: InputDevice
     private lateinit var vendorSpecificKeyboardDevice: InputDevice
     private lateinit var englishDvorakKeyboardDevice: InputDevice
+    private lateinit var englishQwertyKeyboardDevice: InputDevice
 
     @Before
     fun setup() {
@@ -150,17 +154,26 @@
         Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
             .thenReturn(inputManager)
 
-        keyboardDevice = createKeyboard(DEVICE_ID, 0, 0, "", "")
+        keyboardDevice = createKeyboard(DEVICE_ID, DEFAULT_VENDOR_ID, DEFAULT_PRODUCT_ID, "", "")
         vendorSpecificKeyboardDevice = createKeyboard(VENDOR_SPECIFIC_DEVICE_ID, 1, 1, "", "")
-        englishDvorakKeyboardDevice =
-            createKeyboard(ENGLISH_DVORAK_DEVICE_ID, 0, 0, "en", "dvorak")
+        englishDvorakKeyboardDevice = createKeyboard(ENGLISH_DVORAK_DEVICE_ID, DEFAULT_VENDOR_ID,
+                DEFAULT_PRODUCT_ID, "en", "dvorak")
+        englishQwertyKeyboardDevice = createKeyboard(ENGLISH_QWERTY_DEVICE_ID, DEFAULT_VENDOR_ID,
+                DEFAULT_PRODUCT_ID, "en", "qwerty")
         Mockito.`when`(iInputManager.inputDeviceIds)
-            .thenReturn(intArrayOf(DEVICE_ID, VENDOR_SPECIFIC_DEVICE_ID, ENGLISH_DVORAK_DEVICE_ID))
+            .thenReturn(intArrayOf(
+                DEVICE_ID,
+                VENDOR_SPECIFIC_DEVICE_ID,
+                ENGLISH_DVORAK_DEVICE_ID,
+                ENGLISH_QWERTY_DEVICE_ID
+            ))
         Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
         Mockito.`when`(iInputManager.getInputDevice(VENDOR_SPECIFIC_DEVICE_ID))
             .thenReturn(vendorSpecificKeyboardDevice)
         Mockito.`when`(iInputManager.getInputDevice(ENGLISH_DVORAK_DEVICE_ID))
             .thenReturn(englishDvorakKeyboardDevice)
+        Mockito.`when`(iInputManager.getInputDevice(ENGLISH_QWERTY_DEVICE_ID))
+                .thenReturn(englishQwertyKeyboardDevice)
     }
 
     private fun setupBroadcastReceiver() {
@@ -778,14 +791,23 @@
     @Test
     fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() {
         NewSettingsApiFlag(true).use {
-            // Should return English dvorak even if IME current layout is qwerty, since HW says the
+            val frenchSubtype = createImeSubtypeForLanguageTagAndLayoutType("fr", "azerty")
+            // Should return English dvorak even if IME current layout is French, since HW says the
             // keyboard is a Dvorak keyboard
             assertCorrectLayout(
                 englishDvorakKeyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("en", "qwerty"),
+                frenchSubtype,
                 createLayoutDescriptor("keyboard_layout_english_us_dvorak")
             )
 
+            // Back to back changing HW keyboards with same product and vendor ID but different
+            // language and layout type should configure the layouts correctly.
+            assertCorrectLayout(
+                englishQwertyKeyboardDevice,
+                frenchSubtype,
+                createLayoutDescriptor("keyboard_layout_english_us")
+            )
+
             // Fallback to IME information if the HW provided layout script is incompatible with the
             // provided IME subtype
             assertCorrectLayout(
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 01e56a0..1cfaf7c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -228,6 +228,15 @@
                 });
     }
 
+    public void testShortcutIdTruncated() {
+        ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(),
+                "s".repeat(Short.MAX_VALUE)).build();
+
+        assertTrue(
+                "id must be truncated to MAX_ID_LENGTH",
+                si.getId().length() <= ShortcutInfo.MAX_ID_LENGTH);
+    }
+
     public void testShortcutInfoParcel() {
         setCaller(CALLING_PACKAGE_1, USER_10);
         ShortcutInfo si = parceled(new ShortcutInfo.Builder(mClientContext)
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java
new file mode 100644
index 0000000..20e2692
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java
@@ -0,0 +1,580 @@
+/*
+ * 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.pm;
+
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_INCOMPLETE_OR_TIMEOUT;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_NULL_USER_INFO;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_UNSPECIFIED;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_BEGIN;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_CANCEL;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_ERROR;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_FINISH;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_NONE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_GRANT_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_CREATE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_REMOVE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_START;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_STOP;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_CREATE_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_REMOVE_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_REVOKE_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_START_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_STOP_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.pm.UserInfo;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class UserJourneyLoggerTest {
+
+    public static final int FULL_USER_ADMIN_FLAG = 0x00000402;
+    private UserJourneyLogger mUserJourneyLogger;
+
+    @Before
+    public void setup() throws Exception {
+        mUserJourneyLogger = spy(new UserJourneyLogger());
+    }
+
+    @Test
+    public void testUserStartLifecycleJourneyReported() {
+        final UserLifecycleJourneyReportedCaptor report1 = new UserLifecycleJourneyReportedCaptor();
+        final UserJourneyLogger.UserJourneySession session = new UserJourneyLogger
+                .UserJourneySession(10, USER_JOURNEY_USER_START);
+
+        report1.captureLogAndAssert(mUserJourneyLogger, session,
+                USER_JOURNEY_USER_START, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY, 1,
+                ERROR_CODE_UNSPECIFIED);
+    }
+
+
+    @Test
+    public void testUserLifecycleEventOccurred() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = new UserJourneyLogger
+                .UserJourneySession(10, USER_JOURNEY_USER_START);
+
+        report1.captureLogAndAssert(mUserJourneyLogger, session, 0,
+                USER_LIFECYCLE_EVENT_START_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED);
+    }
+
+    @Test
+    public void testLogUserLifecycleEvent() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_START_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        mUserJourneyLogger.logUserLifecycleEvent(10, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
+                EVENT_STATE_NONE);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
+                EVENT_STATE_NONE, ERROR_CODE_UNSPECIFIED, 2);
+    }
+
+
+    @Test
+    public void testCreateUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(-1, USER_JOURNEY_USER_CREATE);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, -1,
+                USER_LIFECYCLE_EVENT_CREATE_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        UserInfo targetUser = new UserInfo(10, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserCreateJourneyFinish(0, targetUser);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_CREATE_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_USER_CREATE, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+    }
+
+    @Test
+    public void testRemoveUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_USER_REMOVE);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_REMOVE_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(10, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
+                USER_JOURNEY_USER_REMOVE);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_REMOVE_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_USER_REMOVE, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+    }
+
+    @Test
+    public void testStartUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(10, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
+                USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_USER_START, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+    }
+
+    @Test
+    public void testStopUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(10, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
+                USER_JOURNEY_USER_STOP);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER, EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_USER_STOP, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+    }
+
+    @Test
+    public void testAbortStopUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(10, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+
+        mUserJourneyLogger.logUserJourneyFinishWithError(-1, targetUser,
+                USER_JOURNEY_USER_STOP, ERROR_CODE_ABORTED);
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER,
+                EVENT_STATE_CANCEL, ERROR_CODE_ABORTED, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_USER_STOP, -1, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_ABORTED, 1);
+    }
+
+    @Test
+    public void testIncompleteStopUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        mUserJourneyLogger.finishAndClearIncompleteUserJourney(10, USER_JOURNEY_USER_STOP);
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER,
+                EVENT_STATE_ERROR,
+                ERROR_CODE_INCOMPLETE_OR_TIMEOUT, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_USER_STOP, -1, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN,
+                -1, // information about user are incomplete
+                ERROR_CODE_INCOMPLETE_OR_TIMEOUT, 1);
+    }
+
+    @Test
+    public void testGrantAdminUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_GRANT_ADMIN);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(10, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
+                USER_JOURNEY_GRANT_ADMIN);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_GRANT_ADMIN, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+    }
+
+    @Test
+    public void testNullUserErrorGrantAdminUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+
+        UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_GRANT_ADMIN);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        mUserJourneyLogger.logNullUserJourneyError(USER_JOURNEY_GRANT_ADMIN,
+                0, 10, "", -1);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
+                EVENT_STATE_ERROR, ERROR_CODE_NULL_USER_INFO, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_GRANT_ADMIN, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN,
+                -1, ERROR_CODE_NULL_USER_INFO, 1);
+    }
+
+    @Test
+    public void testRevokeAdminUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_REVOKE_ADMIN,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(10, "test target user", UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
+                UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_REVOKE_ADMIN,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000400, ERROR_CODE_UNSPECIFIED, 1);
+    }
+
+    @Test
+    public void testSwitchFGUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(11, UserJourneyLogger.USER_JOURNEY_USER_SWITCH_FG);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserJourneyLogger.UserJourneySession session2 = mUserJourneyLogger
+                .logUserJourneyBegin(11, USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 2);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(11, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(10, targetUser,
+                USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 3);
+
+        report2.captureAndAssert(mUserJourneyLogger, session2.mSessionId,
+                USER_JOURNEY_USER_START, 10, 11,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+
+        mUserJourneyLogger.logUserSwitchJourneyFinish(10, targetUser);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 4);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                UserJourneyLogger.USER_JOURNEY_USER_SWITCH_FG, 10, 11,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 2);
+    }
+
+
+    @Test
+    public void testSwitchUIUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(11, UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserJourneyLogger.UserJourneySession session2 = mUserJourneyLogger
+                .logUserJourneyBegin(11, USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 2);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        UserInfo targetUser = new UserInfo(11, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(10, targetUser,
+                USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 3);
+
+        report2.captureAndAssert(mUserJourneyLogger, session2.mSessionId,
+                USER_JOURNEY_USER_START, 10, 11,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+
+        mUserJourneyLogger.logUserSwitchJourneyFinish(10, targetUser);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+                EVENT_STATE_FINISH,
+                ERROR_CODE_UNSPECIFIED, 4);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI, 10, 11,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 2);
+    }
+
+
+    @Test
+    public void testSwitchWithStopUIUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+
+        // BEGIN USER SWITCH
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(11, UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        // BEGIN USER STOP
+        final  UserJourneyLogger.UserJourneySession session2 = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
+
+        report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 2);
+
+        // BEGIN USER START
+        UserJourneyLogger.UserJourneySession session3 = mUserJourneyLogger
+                .logUserJourneyBegin(11, USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session3.mSessionId, 11,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 3);
+
+
+        // FINISH USER STOP
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(10, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(-1, targetUser,
+                USER_JOURNEY_USER_STOP);
+
+        report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 4);
+
+        report2.captureAndAssert(mUserJourneyLogger, session2.mSessionId,
+                USER_JOURNEY_USER_STOP, -1, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                FULL_USER_ADMIN_FLAG, ERROR_CODE_UNSPECIFIED, 1);
+
+        // FINISH USER START
+        final UserInfo targetUser2 = new UserInfo(11, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(10, targetUser2,
+                USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session3.mSessionId, 11,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 5);
+
+        report2.captureAndAssert(mUserJourneyLogger, session3.mSessionId,
+                USER_JOURNEY_USER_START, 10, 11,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                FULL_USER_ADMIN_FLAG, ERROR_CODE_UNSPECIFIED, 2);
+
+
+        // FINISH USER SWITCH
+        mUserJourneyLogger.logUserSwitchJourneyFinish(10, targetUser2);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 6);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI, 10, 11,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 3);
+    }
+
+    static class UserLifecycleJourneyReportedCaptor {
+        ArgumentCaptor<Long> mSessionId = ArgumentCaptor.forClass(Long.class);
+        ArgumentCaptor<Integer> mJourney = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mOriginalUserId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mTargetUserId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mUserType = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mUserFlags = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mErrorCode = ArgumentCaptor.forClass(Integer.class);
+
+        public void captureAndAssert(UserJourneyLogger mUserJourneyLogger,
+                long sessionId, int journey, int originalUserId,
+                int targetUserId, int userType, int userFlags, int errorCode, int times) {
+            verify(mUserJourneyLogger, times(times))
+                    .writeUserLifecycleJourneyReported(mSessionId.capture(),
+                            mJourney.capture(),
+                            mOriginalUserId.capture(),
+                            mTargetUserId.capture(),
+                            mUserType.capture(),
+                            mUserFlags.capture(),
+                            mErrorCode.capture());
+
+            assertThat(mSessionId.getValue()).isEqualTo(sessionId);
+            assertThat(mJourney.getValue()).isEqualTo(journey);
+            assertThat(mOriginalUserId.getValue()).isEqualTo(originalUserId);
+            assertThat(mTargetUserId.getValue()).isEqualTo(targetUserId);
+            assertThat(mUserType.getValue()).isEqualTo(userType);
+            assertThat(mUserFlags.getValue()).isEqualTo(userFlags);
+            assertThat(mErrorCode.getValue()).isEqualTo(errorCode);
+        }
+
+
+        public void captureLogAndAssert(UserJourneyLogger mUserJourneyLogger,
+                UserJourneyLogger.UserJourneySession session, int journey, int originalUserId,
+                int targetUserId, int userType, int userFlags, int errorCode) {
+            mUserJourneyLogger.logUserLifecycleJourneyReported(session, journey, originalUserId,
+                    targetUserId, userType, userFlags, errorCode);
+
+            captureAndAssert(mUserJourneyLogger, session.mSessionId, journey, originalUserId,
+                    targetUserId, userType, userFlags, errorCode, 1);
+        }
+    }
+
+
+    static class UserLifecycleEventOccurredCaptor {
+        ArgumentCaptor<Long> mSessionId = ArgumentCaptor.forClass(Long.class);
+        ArgumentCaptor<Integer> mTargetUserId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mEvent = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mStste = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mErrorCode = ArgumentCaptor.forClass(Integer.class);
+
+
+        public void captureAndAssert(UserJourneyLogger mUserJourneyLogger,
+                long sessionId, int targetUserId, int event, int state, int errorCode, int times) {
+            verify(mUserJourneyLogger, times(times))
+                    .writeUserLifecycleEventOccurred(mSessionId.capture(),
+                            mTargetUserId.capture(),
+                            mEvent.capture(),
+                            mStste.capture(),
+                            mErrorCode.capture());
+
+            assertThat(mSessionId.getValue()).isEqualTo(sessionId);
+            assertThat(mTargetUserId.getValue()).isEqualTo(targetUserId);
+            assertThat(mEvent.getValue()).isEqualTo(event);
+            assertThat(mStste.getValue()).isEqualTo(state);
+            assertThat(mErrorCode.getValue()).isEqualTo(errorCode);
+        }
+
+
+        public void captureLogAndAssert(UserJourneyLogger mUserJourneyLogger,
+                UserJourneyLogger.UserJourneySession session, int targetUserId, int event,
+                int state, int errorCode) {
+            mUserJourneyLogger.logUserLifecycleEventOccurred(session, targetUserId, event,
+                    state, errorCode);
+
+            captureAndAssert(mUserJourneyLogger, session.mSessionId, targetUserId, event,
+                    state, errorCode, 1);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 52bf244..ae3ceb1 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.os.PowerManager.USER_ACTIVITY_EVENT_BUTTON;
 import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
 import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
 import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
@@ -41,6 +42,7 @@
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -112,6 +114,7 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
 
+import java.time.Duration;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Executor;
@@ -2468,4 +2471,18 @@
         verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
                 anyInt(), any(), any(), same(callback2));
     }
+
+    @Test
+    public void testUserActivity_futureEventsAreIgnored() {
+        createService();
+        startSystem();
+        // Starting the system triggers a user activity event, so clear that before calling
+        // userActivity() directly.
+        clearInvocations(mNotifierMock);
+        final long eventTime = mClock.now() + Duration.ofHours(10).toMillis();
+        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, eventTime,
+                USER_ACTIVITY_EVENT_BUTTON, /* flags= */ 0);
+        verify(mNotifierMock, never()).onUserActivity(anyInt(),  anyInt(), anyInt());
+    }
+
 }
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/DeviceStateHandlerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/DeviceStateHandlerTest.java
new file mode 100644
index 0000000..089bd45
--- /dev/null
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/DeviceStateHandlerTest.java
@@ -0,0 +1,261 @@
+/*
+ * 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 android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_DISABLED;
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY;
+
+import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState;
+import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState.*;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.SystemClock;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.EventLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public final class DeviceStateHandlerTest {
+    private final long CONFIRM_NO_EVENT_WAIT_MS = 1000;
+    // A wait substantially less than the duration we delay phone notifications by
+    private final long PHONE_DELAY_BRIEF_WAIT_MS =
+            DeviceStateHandler.CALL_INACTIVE_MSG_DELAY_MS / 4;
+
+    private DeviceStateHandler mHandler;
+    private DeviceStateHandler.DeviceStateListener mDeviceStateCallback;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private SoundTriggerDeviceState mState;
+
+    @GuardedBy("mLock")
+    private CountDownLatch mLatch;
+
+    private EventLogger mEventLogger;
+
+    @Before
+    public void setup() {
+        // Reset the state prior to each test
+        mEventLogger = new EventLogger(256, "test logger");
+        synchronized (mLock) {
+            mLatch = new CountDownLatch(1);
+        }
+        mDeviceStateCallback =
+                (SoundTriggerDeviceState state) -> {
+                    synchronized (mLock) {
+                        mState = state;
+                        mLatch.countDown();
+                    }
+                };
+        mHandler = new DeviceStateHandler(Runnable::run, mEventLogger);
+        mHandler.onPhoneCallStateChanged(false);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+        mHandler.registerListener(mDeviceStateCallback);
+        try {
+            waitAndAssertState(ENABLE);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void waitAndAssertState(SoundTriggerDeviceState state) throws InterruptedException {
+        CountDownLatch latch;
+        synchronized (mLock) {
+            latch = mLatch;
+        }
+        latch.await();
+        synchronized (mLock) {
+            assertThat(mState).isEqualTo(state);
+            mLatch = new CountDownLatch(1);
+        }
+    }
+
+    private void waitToConfirmNoEventReceived() throws InterruptedException {
+        CountDownLatch latch;
+        synchronized (mLock) {
+            latch = mLatch;
+        }
+        // Check that we time out
+        assertThat(latch.await(CONFIRM_NO_EVENT_WAIT_MS, TimeUnit.MILLISECONDS)).isFalse();
+    }
+
+    @Test
+    public void onPowerModeChangedCritical_receiveStateChange() throws Exception {
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitAndAssertState(CRITICAL);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+        waitAndAssertState(ENABLE);
+    }
+
+    @Test
+    public void onPowerModeChangedDisabled_receiveStateChange() throws Exception {
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+        waitAndAssertState(DISABLE);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+        waitAndAssertState(ENABLE);
+    }
+
+    @Test
+    public void onPowerModeChangedMultiple_receiveStateChange() throws Exception {
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+        waitAndAssertState(DISABLE);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitAndAssertState(CRITICAL);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+        waitAndAssertState(DISABLE);
+    }
+
+    @Test
+    public void onPowerModeSameState_noStateChange() throws Exception {
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+        waitAndAssertState(DISABLE);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+        waitToConfirmNoEventReceived();
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitAndAssertState(CRITICAL);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitToConfirmNoEventReceived();
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+        waitAndAssertState(ENABLE);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+        waitToConfirmNoEventReceived();
+    }
+
+    @Test
+    public void onPhoneCall_receiveStateChange() throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        mHandler.onPhoneCallStateChanged(false);
+        waitAndAssertState(ENABLE);
+    }
+
+    @Test
+    public void onPhoneCall_receiveStateChangeIsDelayed() throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        long beforeTime = SystemClock.uptimeMillis();
+        mHandler.onPhoneCallStateChanged(false);
+        waitAndAssertState(ENABLE);
+        long afterTime = SystemClock.uptimeMillis();
+        assertThat(afterTime - beforeTime).isAtLeast(DeviceStateHandler.CALL_INACTIVE_MSG_DELAY_MS);
+    }
+
+    @Test
+    public void onPhoneCallEnterExitEnter_receiveNoStateChange() throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        mHandler.onPhoneCallStateChanged(false);
+        SystemClock.sleep(PHONE_DELAY_BRIEF_WAIT_MS);
+        mHandler.onPhoneCallStateChanged(true);
+        waitToConfirmNoEventReceived();
+    }
+
+    @Test
+    public void onBatteryCallbackDuringPhoneWait_receiveStateChangeDelayed() throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        mHandler.onPhoneCallStateChanged(false);
+        SystemClock.sleep(PHONE_DELAY_BRIEF_WAIT_MS);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitAndAssertState(CRITICAL);
+        // Ensure we don't get an ENABLE event after
+        waitToConfirmNoEventReceived();
+    }
+
+    @Test
+    public void onBatteryChangeWhenInPhoneCall_receiveNoStateChange() throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+        waitToConfirmNoEventReceived();
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitToConfirmNoEventReceived();
+    }
+
+    @Test
+    public void whenBatteryCriticalChangeDuringCallAfterPhoneCall_receiveCriticalStateChange()
+            throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitToConfirmNoEventReceived();
+        mHandler.onPhoneCallStateChanged(false);
+        waitAndAssertState(CRITICAL);
+    }
+
+    @Test
+    public void whenBatteryDisableDuringCallAfterPhoneCallBatteryEnable_receiveStateChange()
+            throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+        waitToConfirmNoEventReceived();
+        mHandler.onPhoneCallStateChanged(false);
+        waitToConfirmNoEventReceived();
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitAndAssertState(CRITICAL);
+    }
+
+    @Test
+    public void whenPhoneCallDuringBatteryDisable_receiveNoStateChange() throws Exception {
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+        waitAndAssertState(DISABLE);
+        mHandler.onPhoneCallStateChanged(true);
+        waitToConfirmNoEventReceived();
+        mHandler.onPhoneCallStateChanged(false);
+        waitToConfirmNoEventReceived();
+    }
+
+    @Test
+    public void whenPhoneCallDuringBatteryCritical_receiveStateChange() throws Exception {
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitAndAssertState(CRITICAL);
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        mHandler.onPhoneCallStateChanged(false);
+        waitAndAssertState(CRITICAL);
+    }
+
+    // This test could be flaky, but we want to verify that we only delay notification if
+    // we are exiting a call, NOT if we are entering a call.
+    @FlakyTest
+    @Test
+    public void whenPhoneCallReceived_receiveStateChangeFast() throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        CountDownLatch latch;
+        synchronized (mLock) {
+            latch = mLatch;
+        }
+        assertThat(latch.await(PHONE_DELAY_BRIEF_WAIT_MS, TimeUnit.MILLISECONDS)).isTrue();
+        synchronized (mLock) {
+            assertThat(mState).isEqualTo(DISABLE);
+        }
+    }
+}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
index 2d0755d..3ac9a27 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.soundtrigger_middleware;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -34,12 +36,12 @@
 import static org.mockito.Mockito.when;
 
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.Status;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.HwParcel;
 import android.os.IBinder;
 import android.os.IHwBinder;
@@ -617,13 +619,16 @@
             final int handle = 85;
             final int status =
                     android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.ABORT;
-            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    RecognitionEvent.class);
+            ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEventSys.class);
 
             hwCallback.recognitionCallback(TestUtil.createRecognitionEvent_2_0(handle, status), 99);
             mCanonical.flushCallbacks();
             verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
-            TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED,
+            RecognitionEventSys lastEvent = eventCaptor.getValue();
+            assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+            TestUtil.validateRecognitionEvent(lastEvent.recognitionEvent,
+                    RecognitionStatus.ABORTED,
                     false);
         }
 
@@ -631,14 +636,16 @@
             final int handle = 92;
             final int status =
                     android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS;
-            ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    PhraseRecognitionEvent.class);
+            ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    PhraseRecognitionEventSys.class);
 
             hwCallback.phraseRecognitionCallback(
                     TestUtil.createPhraseRecognitionEvent_2_0(handle, status), 99);
             mCanonical.flushCallbacks();
             verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
-            TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
+            PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
+            assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+            TestUtil.validatePhraseRecognitionEvent(lastEvent.phraseRecognitionEvent,
                     RecognitionStatus.SUCCESS, false);
         }
         verifyNoMoreInteractions(canonicalCallback);
@@ -652,28 +659,34 @@
             final int handle = 85;
             final int status =
                     android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.ABORT;
-            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    RecognitionEvent.class);
+            ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEventSys.class);
 
             hwCallback.recognitionCallback_2_1(TestUtil.createRecognitionEvent_2_1(handle, status),
                     99);
             mCanonical.flushCallbacks();
             verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
-            TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED,
+            RecognitionEventSys lastEvent = eventCaptor.getValue();
+            assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+            TestUtil.validateRecognitionEvent(lastEvent.recognitionEvent,
+                    RecognitionStatus.ABORTED,
                     false);
         }
 
         {
             final int handle = 87;
             final int status = 3; // FORCED;
-            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    RecognitionEvent.class);
+            ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEventSys.class);
 
             hwCallback.recognitionCallback_2_1(TestUtil.createRecognitionEvent_2_1(handle, status),
                     99);
             mCanonical.flushCallbacks();
             verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
-            TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED,
+            RecognitionEventSys lastEvent = eventCaptor.getValue();
+            assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+            TestUtil.validateRecognitionEvent(lastEvent.recognitionEvent,
+                    RecognitionStatus.FORCED,
                     true);
         }
 
@@ -681,28 +694,32 @@
             final int handle = 92;
             final int status =
                     android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS;
-            ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    PhraseRecognitionEvent.class);
+            ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    PhraseRecognitionEventSys.class);
 
             hwCallback.phraseRecognitionCallback_2_1(
                     TestUtil.createPhraseRecognitionEvent_2_1(handle, status), 99);
             mCanonical.flushCallbacks();
             verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
-            TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
+            PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
+            assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+            TestUtil.validatePhraseRecognitionEvent(lastEvent.phraseRecognitionEvent,
                     RecognitionStatus.SUCCESS, false);
         }
 
         {
             final int handle = 102;
             final int status = 3; // FORCED;
-            ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    PhraseRecognitionEvent.class);
+            ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    PhraseRecognitionEventSys.class);
 
             hwCallback.phraseRecognitionCallback_2_1(
                     TestUtil.createPhraseRecognitionEvent_2_1(handle, status), 99);
             mCanonical.flushCallbacks();
             verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
-            TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
+            PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
+            assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+            TestUtil.validatePhraseRecognitionEvent(lastEvent.phraseRecognitionEvent,
                     RecognitionStatus.FORCED, true);
         }
         verifyNoMoreInteractions(canonicalCallback);
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
index 6198925..9a59ede 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
@@ -30,8 +30,8 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionStatus;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 
 import androidx.annotation.NonNull;
 
@@ -68,13 +68,14 @@
 
         mNotifier.setActive(true);
         verify(mUnderlying).stopRecognition(handle);
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
+        ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEventSys.class);
         Thread.sleep(50);
         verify(callback).recognitionCallback(eq(handle), eventCaptor.capture());
-        RecognitionEvent event = eventCaptor.getValue();
-        assertEquals(event.status, RecognitionStatus.ABORTED);
-        assertFalse(event.recognitionStillActive);
+        RecognitionEventSys event = eventCaptor.getValue();
+        assertEquals(event.halEventReceivedMillis, -1);
+        assertEquals(event.recognitionEvent.status, RecognitionStatus.ABORTED);
+        assertFalse(event.recognitionEvent.recognitionStillActive);
         verifyZeroInteractions(mGlobalCallback);
         clearInvocations(callback, mUnderlying);
 
@@ -116,8 +117,11 @@
 
         mNotifier.setActive(true);
         verify(mUnderlying, times(1)).stopRecognition(handle);
+        ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEventSys.class);
         mHandler.stopRecognition(handle);
-        verify(callback, times(1)).recognitionCallback(eq(handle), any());
+        verify(callback).recognitionCallback(eq(handle), eventCaptor.capture());
+        assertEquals(eventCaptor.getValue().halEventReceivedMillis, -1);
     }
 
     @Test(timeout = 200)
@@ -133,19 +137,21 @@
         verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
 
         doAnswer(invocation -> {
-            RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.ABORTED,
+            RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+            recognitionEventSys.recognitionEvent = TestUtil.createRecognitionEvent(
+                    RecognitionStatus.ABORTED,
                     false);
+            recognitionEventSys.halEventReceivedMillis = 12345;
             // Call the callback from a different thread to detect deadlocks by preventing recursive
             // locking from working.
-            runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+            runOnSeparateThread(
+                    () -> modelCallback.recognitionCallback(handle, recognitionEventSys));
             return null;
         }).when(mUnderlying).stopRecognition(handle);
         mHandler.stopRecognition(handle);
         verify(mUnderlying, times(1)).stopRecognition(handle);
 
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
-        verify(callback, atMost(1)).recognitionCallback(eq(handle), eventCaptor.capture());
+        verify(callback, atMost(1)).recognitionCallback(eq(handle), any(RecognitionEventSys.class));
     }
 
     @Test(timeout = 200)
@@ -162,11 +168,15 @@
 
         doAnswer(invocation -> {
             // The stop request causes a callback to be flushed.
-            RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+            RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+            recognitionEventSys.recognitionEvent = TestUtil.createRecognitionEvent(
+                    RecognitionStatus.FORCED,
                     true);
+            recognitionEventSys.halEventReceivedMillis = 12345;
             // Call the callback from a different thread to detect deadlocks by preventing recursive
             // locking from working.
-            runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+            runOnSeparateThread(
+                    () -> modelCallback.recognitionCallback(handle, recognitionEventSys));
             // While the HAL is processing the stop request, capture state becomes active.
             new Thread(() -> mNotifier.setActive(true)).start();
             Thread.sleep(50);
@@ -194,11 +204,15 @@
 
         doAnswer(invocation -> {
             // The stop request causes a callback to be flushed.
-            RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+            RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+            recognitionEventSys.recognitionEvent = TestUtil.createRecognitionEvent(
+                    RecognitionStatus.FORCED,
                     true);
+            recognitionEventSys.halEventReceivedMillis = 12345;
             // Call the callback from a different thread to detect deadlocks by preventing recursive
             // locking from working.
-            runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+            runOnSeparateThread(
+                    () -> modelCallback.recognitionCallback(handle, recognitionEventSys));
             // While the HAL is processing the stop request, client requests stop.
             new Thread(() -> mHandler.stopRecognition(handle)).start();
             Thread.sleep(50);
@@ -223,23 +237,22 @@
         verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
 
         doAnswer(invocation -> {
-            RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.SUCCESS,
+            RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+            recognitionEventSys.recognitionEvent = TestUtil.createRecognitionEvent(
+                    RecognitionStatus.SUCCESS,
                     false);
+            recognitionEventSys.halEventReceivedMillis = 12345;
             // Call the callback from a different thread to detect deadlocks by preventing recursive
             // locking from working.
-            runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+            runOnSeparateThread(
+                    () -> modelCallback.recognitionCallback(handle, recognitionEventSys));
             return null;
         }).when(mUnderlying).stopRecognition(handle);
         mNotifier.setActive(true);
         verify(mUnderlying, times(1)).stopRecognition(handle);
         Thread.sleep(50);
 
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
-        verify(callback, atMost(2)).recognitionCallback(eq(handle), eventCaptor.capture());
-        RecognitionEvent lastEvent = eventCaptor.getValue();
-        assertEquals(lastEvent.status, RecognitionStatus.ABORTED);
-        assertFalse(lastEvent.recognitionStillActive);
+        verify(callback, atMost(2)).recognitionCallback(eq(handle), any());
     }
 
 
@@ -256,11 +269,15 @@
         verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
 
         doAnswer(invocation -> {
-            RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+            RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+            recognitionEventSys.recognitionEvent = TestUtil.createRecognitionEvent(
+                    RecognitionStatus.FORCED,
                     true);
+            recognitionEventSys.halEventReceivedMillis = 12345;
             // Call the callback from a different thread to detect deadlocks by preventing recursive
             // locking from working.
-            runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+            runOnSeparateThread(
+                    () -> modelCallback.recognitionCallback(handle, recognitionEventSys));
 
             return null;
         }).when(mUnderlying).stopRecognition(handle);
@@ -268,12 +285,7 @@
         verify(mUnderlying, times(1)).stopRecognition(handle);
 
         Thread.sleep(50);
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
-        verify(callback, atMost(2)).recognitionCallback(eq(handle), eventCaptor.capture());
-        RecognitionEvent lastEvent = eventCaptor.getValue();
-        assertEquals(lastEvent.status, RecognitionStatus.ABORTED);
-        assertFalse(lastEvent.recognitionStillActive);
+        verify(callback, atMost(2)).recognitionCallback(eq(handle), any());
     }
 
     private static void runOnSeparateThread(Runnable runnable) {
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 3bebc94..5a2451f 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -30,18 +30,19 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.ElapsedRealtimeLong;
 import android.media.soundtrigger.ModelParameter;
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.os.RemoteException;
 import android.util.Pair;
@@ -224,10 +225,12 @@
         // Stop the recognition.
         stopRecognition(module, handle, hwHandle);
 
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
+        ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEventSys.class);
         verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
-        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+        RecognitionEventSys lastEvent = eventCaptor.getValue();
+        assertEquals(-1, lastEvent.halEventReceivedMillis);
+        assertEquals(RecognitionStatus.ABORTED, lastEvent.recognitionEvent.status);
 
         // Unload the model.
         unloadModel(module, handle, hwHandle);
@@ -273,10 +276,12 @@
         // Stop the recognition.
         stopRecognition(module, handle, hwHandle);
 
-        ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                PhraseRecognitionEvent.class);
+        ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                PhraseRecognitionEventSys.class);
         verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
-        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+        PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
+        assertEquals(-1, lastEvent.halEventReceivedMillis);
+        assertEquals(RecognitionStatus.ABORTED, lastEvent.phraseRecognitionEvent.common.status);
 
         // Unload the model.
         unloadModel(module, handle, hwHandle);
@@ -299,11 +304,11 @@
 
         {
             // Signal a capture from the driver (with "still active").
-            RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
-                    RecognitionStatus.SUCCESS, true);
+            RecognitionEventSys event = hwCallback.sendRecognitionEvent(hwHandle,
+                    RecognitionStatus.SUCCESS, true, 12345);
 
-            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    RecognitionEvent.class);
+            ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEventSys.class);
             verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
             // Validate the event.
@@ -312,11 +317,11 @@
 
         {
             // Signal a capture from the driver (without "still active").
-            RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
-                    RecognitionStatus.SUCCESS, false);
+            RecognitionEventSys event = hwCallback.sendRecognitionEvent(hwHandle,
+                    RecognitionStatus.SUCCESS, false, 12345);
 
-            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    RecognitionEvent.class);
+            ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEventSys.class);
             verify(callback, times(2)).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
             // Validate the event.
@@ -343,11 +348,11 @@
         startRecognition(module, handle, hwHandle);
 
         // Signal a capture from the driver.
-        PhraseRecognitionEvent event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
-                RecognitionStatus.SUCCESS, false);
+        PhraseRecognitionEventSys event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
+                RecognitionStatus.SUCCESS, false, 12345);
 
-        ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                PhraseRecognitionEvent.class);
+        ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                PhraseRecognitionEventSys.class);
         verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
@@ -377,11 +382,11 @@
         verify(mHalDriver).forceRecognitionEvent(hwHandle);
 
         // Signal a capture from the driver.
-        RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
-                RecognitionStatus.FORCED, true);
+        RecognitionEventSys event = hwCallback.sendRecognitionEvent(hwHandle,
+                RecognitionStatus.FORCED, true, 12345);
 
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
+        ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEventSys.class);
         verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
@@ -445,11 +450,11 @@
         verify(mHalDriver).forceRecognitionEvent(hwHandle);
 
         // Signal a capture from the driver.
-        PhraseRecognitionEvent event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
-                RecognitionStatus.FORCED, true);
+        PhraseRecognitionEventSys event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
+                RecognitionStatus.FORCED, true, 12345);
 
-        ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                PhraseRecognitionEvent.class);
+        ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                PhraseRecognitionEventSys.class);
         verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
@@ -510,14 +515,16 @@
         startRecognition(module, handle, hwHandle);
 
         // Abort.
-        hwCallback.sendRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false);
+        hwCallback.sendRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false, 12345);
 
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
+        ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEventSys.class);
         verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
-        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+        RecognitionEventSys lastEvent = eventCaptor.getValue();
+        assertEquals(12345, lastEvent.halEventReceivedMillis);
+        assertEquals(RecognitionStatus.ABORTED, lastEvent.recognitionEvent.status);
 
         // Unload the model.
         unloadModel(module, handle, hwHandle);
@@ -540,14 +547,16 @@
         startRecognition(module, handle, hwHandle);
 
         // Abort.
-        hwCallback.sendPhraseRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false);
+        hwCallback.sendPhraseRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false, 12345);
 
-        ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                PhraseRecognitionEvent.class);
+        ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                PhraseRecognitionEventSys.class);
         verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
-        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+        PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
+        assertEquals(12345, lastEvent.halEventReceivedMillis);
+        assertEquals(RecognitionStatus.ABORTED, lastEvent.phraseRecognitionEvent.common.status);
 
         // Unload the model.
         unloadModel(module, handle, hwHandle);
@@ -630,18 +639,24 @@
             mCallback = callback;
         }
 
-        private RecognitionEvent sendRecognitionEvent(int hwHandle, @RecognitionStatus int status,
-                boolean recognitionStillActive) {
-            RecognitionEvent event = TestUtil.createRecognitionEvent(status,
+        private RecognitionEventSys sendRecognitionEvent(int hwHandle,
+                @RecognitionStatus int status,
+                boolean recognitionStillActive, @ElapsedRealtimeLong long halEventReceivedMillis) {
+            RecognitionEventSys event = new RecognitionEventSys();
+            event.recognitionEvent = TestUtil.createRecognitionEvent(status,
                     recognitionStillActive);
+            event.halEventReceivedMillis = halEventReceivedMillis;
             mCallback.recognitionCallback(hwHandle, event);
             return event;
         }
 
-        private PhraseRecognitionEvent sendPhraseRecognitionEvent(int hwHandle,
-                @RecognitionStatus int status, boolean recognitionStillActive) {
-            PhraseRecognitionEvent event = TestUtil.createPhraseRecognitionEvent(status,
+        private PhraseRecognitionEventSys sendPhraseRecognitionEvent(int hwHandle,
+                @RecognitionStatus int status, boolean recognitionStillActive,
+                @ElapsedRealtimeLong long halEventReceivedMillis) {
+            PhraseRecognitionEventSys event = new PhraseRecognitionEventSys();
+            event.phraseRecognitionEvent = TestUtil.createPhraseRecognitionEvent(status,
                     recognitionStillActive);
+            event.halEventReceivedMillis = halEventReceivedMillis;
             mCallback.phraseRecognitionCallback(hwHandle, event);
             return event;
         }
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
index f8a068c..cc357d7 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
@@ -32,7 +32,7 @@
 import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
-import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
 import android.os.BatteryStatsInternal;
 import android.os.Process;
 import android.os.RemoteException;
@@ -51,8 +51,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Optional;
-
 @RunWith(JUnit4.class)
 public class SoundTriggerMiddlewareLoggingLatencyTest {
 
@@ -63,8 +61,6 @@
     private ISoundTriggerMiddlewareInternal mDelegateMiddleware;
     @Mock
     private ISoundTriggerCallback mISoundTriggerCallback;
-    @Mock
-    private ISoundTriggerModule mSoundTriggerModule;
     private SoundTriggerMiddlewareLogging mSoundTriggerMiddlewareLogging;
 
     @Before
@@ -109,7 +105,7 @@
         verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
 
         triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
-                RecognitionStatus.SUCCESS, Optional.of(100) /* keyphraseId */);
+                RecognitionStatus.SUCCESS, 100 /* keyphraseId */);
 
         assertThat(mLatencyTracker.getActiveActionStartTime(
                 ACTION_SHOW_VOICE_INTERACTION)).isGreaterThan(-1);
@@ -124,11 +120,11 @@
         verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
 
         triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
-                RecognitionStatus.SUCCESS, Optional.of(100) /* keyphraseId */);
+                RecognitionStatus.SUCCESS, 100 /* keyphraseId */);
         long firstTriggerSessionStartTime = mLatencyTracker.getActiveActionStartTime(
                 ACTION_SHOW_VOICE_INTERACTION);
         triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
-                RecognitionStatus.SUCCESS, Optional.of(100) /* keyphraseId */);
+                RecognitionStatus.SUCCESS, 100 /* keyphraseId */);
         assertThat(mLatencyTracker.getActiveActionStartTime(
                 ACTION_SHOW_VOICE_INTERACTION)).isGreaterThan(-1);
         assertThat(mLatencyTracker.getActiveActionStartTime(
@@ -145,7 +141,7 @@
         verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
 
         triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
-                RecognitionStatus.ABORTED, Optional.of(100) /* keyphraseId */);
+                RecognitionStatus.ABORTED, 100 /* keyphraseId */);
 
         assertThat(
                 mLatencyTracker.getActiveActionStartTime(ACTION_SHOW_VOICE_INTERACTION)).isEqualTo(
@@ -162,7 +158,7 @@
         verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
 
         triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
-                RecognitionStatus.SUCCESS, Optional.empty() /* keyphraseId */);
+                RecognitionStatus.SUCCESS);
 
         assertThat(
                 mLatencyTracker.getActiveActionStartTime(ACTION_SHOW_VOICE_INTERACTION)).isEqualTo(
@@ -170,19 +166,27 @@
     }
 
     private void triggerPhraseRecognitionEvent(ISoundTriggerCallback callback,
-            @RecognitionStatus int triggerEventStatus, Optional<Integer> optionalKeyphraseId)
-            throws RemoteException {
+            @RecognitionStatus int triggerEventStatus) throws RemoteException {
+        triggerPhraseRecognitionEvent(callback, triggerEventStatus, -1 /* keyphraseId */);
+    }
+
+    private void triggerPhraseRecognitionEvent(ISoundTriggerCallback callback,
+            @RecognitionStatus int triggerEventStatus, int keyphraseId) throws RemoteException {
         // trigger a phrase recognition to start a latency tracker session
         PhraseRecognitionEvent successEventWithKeyphraseId = new PhraseRecognitionEvent();
         successEventWithKeyphraseId.common = new RecognitionEvent();
         successEventWithKeyphraseId.common.status = triggerEventStatus;
-        if (optionalKeyphraseId.isPresent()) {
+        if (keyphraseId > 0) {
             PhraseRecognitionExtra recognitionExtra = new PhraseRecognitionExtra();
-            recognitionExtra.id = optionalKeyphraseId.get();
+            recognitionExtra.id = keyphraseId;
             successEventWithKeyphraseId.phraseExtras =
                     new PhraseRecognitionExtra[]{recognitionExtra};
         }
-        callback.onPhraseRecognition(0 /* modelHandle */, successEventWithKeyphraseId,
+        PhraseRecognitionEventSys phraseRecognitionEventSys = new PhraseRecognitionEventSys();
+        phraseRecognitionEventSys.phraseRecognitionEvent = successEventWithKeyphraseId;
+        phraseRecognitionEventSys.halEventReceivedMillis = 12345;
+        callback.onPhraseRecognition(0 /* modelHandle */, phraseRecognitionEventSys,
                 0 /* captureSession */);
     }
+
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java b/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
new file mode 100644
index 0000000..cc8dab9
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
@@ -0,0 +1,273 @@
+/*
+ * 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.wm;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
+import static com.android.server.wm.DeviceStateController.DeviceState.FOLDED;
+import static com.android.server.wm.DeviceStateController.DeviceState.HALF_FOLDED;
+import static com.android.server.wm.DeviceStateController.DeviceState.OPEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.window.TransitionRequestInfo.DisplayChange;
+
+import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
+import static com.android.server.wm.DeviceStateController.DeviceState.REAR;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the {@link WindowToken} class.
+ *
+ * Build/Install/Run:
+ * atest WmTests:PhysicalDisplaySwitchTransitionLauncherTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class PhysicalDisplaySwitchTransitionLauncherTest extends WindowTestsBase {
+
+    @Mock
+    DisplayContent mDisplayContent;
+    @Mock
+    Context mContext;
+    @Mock
+    Resources mResources;
+    @Mock
+    ActivityTaskManagerService mActivityTaskManagerService;
+    @Mock
+    TransitionController mTransitionController;
+
+    private PhysicalDisplaySwitchTransitionLauncher mTarget;
+    private float mOriginalAnimationScale;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getResources()).thenReturn(mResources);
+        mTarget = new PhysicalDisplaySwitchTransitionLauncher(mDisplayContent,
+                mActivityTaskManagerService, mContext, mTransitionController);
+        mOriginalAnimationScale = ValueAnimator.getDurationScale();
+    }
+
+    @After
+    public void after() {
+        ValueAnimator.setDurationScale(mOriginalAnimationScale);
+    }
+
+    @Test
+    public void testDisplaySwitchAfterUnfoldToOpen_animationsEnabled_requestsTransition() {
+        givenAllAnimationsEnabled();
+        mTarget.foldStateChanged(FOLDED);
+
+        mTarget.foldStateChanged(OPEN);
+        mTarget.requestDisplaySwitchTransitionIfNeeded(
+                /* displayId= */ 123,
+                /* oldDisplayWidth= */ 100,
+                /* oldDisplayHeight= */ 150,
+                /* newDisplayWidth= */ 200,
+                /* newDisplayHeight= */ 250
+        );
+
+        ArgumentCaptor<DisplayChange> displayChangeArgumentCaptor =
+                ArgumentCaptor.forClass(DisplayChange.class);
+        verify(mTransitionController).requestTransitionIfNeeded(eq(TRANSIT_CHANGE), /* flags= */
+                eq(0), eq(mDisplayContent), eq(mDisplayContent), /* remoteTransition= */ isNull(),
+                displayChangeArgumentCaptor.capture());
+        assertThat(displayChangeArgumentCaptor.getValue().getDisplayId()).isEqualTo(123);
+        assertThat(displayChangeArgumentCaptor.getValue().getStartAbsBounds()).isEqualTo(
+                new Rect(0, 0, 100, 150));
+        assertThat(displayChangeArgumentCaptor.getValue().getEndAbsBounds()).isEqualTo(
+                new Rect(0, 0, 200, 250));
+    }
+
+    @Test
+    public void testDisplaySwitchAfterFolding_animationEnabled_doesNotRequestTransition() {
+        givenAllAnimationsEnabled();
+        mTarget.foldStateChanged(OPEN);
+
+        mTarget.foldStateChanged(FOLDED);
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+    @Test
+    public void testDisplaySwitchAfterUnfoldingToHalf_animationEnabled_requestsTransition() {
+        givenAllAnimationsEnabled();
+        mTarget.foldStateChanged(FOLDED);
+
+        mTarget.foldStateChanged(HALF_FOLDED);
+        requestDisplaySwitch();
+
+        assertTransitionRequested();
+    }
+
+    @Test
+    public void testDisplaySwitchSecondTimeAfterUnfolding_animationEnabled_noTransition() {
+        givenAllAnimationsEnabled();
+        mTarget.foldStateChanged(FOLDED);
+        mTarget.foldStateChanged(OPEN);
+        requestDisplaySwitch();
+        clearInvocations(mTransitionController);
+
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+
+    @Test
+    public void testDisplaySwitchAfterGoingToRearAndBack_animationEnabled_noTransition() {
+        givenAllAnimationsEnabled();
+        mTarget.foldStateChanged(OPEN);
+
+        mTarget.foldStateChanged(REAR);
+        mTarget.foldStateChanged(OPEN);
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+    @Test
+    public void testDisplaySwitchAfterUnfoldingAndFolding_animationEnabled_noTransition() {
+        givenAllAnimationsEnabled();
+        mTarget.foldStateChanged(FOLDED);
+        mTarget.foldStateChanged(OPEN);
+        // No request display switch event (simulate very fast fold after unfold, even before
+        // the displays switched)
+        mTarget.foldStateChanged(FOLDED);
+
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+    @Test
+    public void testDisplaySwitch_whenShellTransitionsNotEnabled_noTransition() {
+        givenAllAnimationsEnabled();
+        givenShellTransitionsEnabled(false);
+        mTarget.foldStateChanged(FOLDED);
+
+        mTarget.foldStateChanged(OPEN);
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+    @Test
+    public void testDisplaySwitch_whenAnimationsDisabled_noTransition() {
+        givenAllAnimationsEnabled();
+        givenAnimationsEnabled(false);
+        mTarget.foldStateChanged(FOLDED);
+
+        mTarget.foldStateChanged(OPEN);
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+    @Test
+    public void testDisplaySwitch_whenUnfoldAnimationDisabled_noTransition() {
+        givenAllAnimationsEnabled();
+        givenUnfoldTransitionEnabled(false);
+        mTarget.foldStateChanged(FOLDED);
+
+        mTarget.foldStateChanged(OPEN);
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+    @Test
+    public void testDisplaySwitch_whenNoContentInDisplayContent_noTransition() {
+        givenAllAnimationsEnabled();
+        givenDisplayContentHasContent(false);
+        mTarget.foldStateChanged(FOLDED);
+
+        mTarget.foldStateChanged(OPEN);
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+    private void assertTransitionRequested() {
+        verify(mTransitionController).requestTransitionIfNeeded(anyInt(), anyInt(), any(), any(),
+                any(), any());
+    }
+
+    private void assertTransitionNotRequested() {
+        verify(mTransitionController, never()).requestTransitionIfNeeded(anyInt(), anyInt(), any(),
+                any(), any(), any());
+    }
+
+    private void requestDisplaySwitch() {
+        mTarget.requestDisplaySwitchTransitionIfNeeded(
+                /* displayId= */ 123,
+                /* oldDisplayWidth= */ 100,
+                /* oldDisplayHeight= */ 150,
+                /* newDisplayWidth= */ 200,
+                /* newDisplayHeight= */ 250
+        );
+    }
+
+    private void givenAllAnimationsEnabled() {
+        givenAnimationsEnabled(true);
+        givenUnfoldTransitionEnabled(true);
+        givenShellTransitionsEnabled(true);
+        givenDisplayContentHasContent(true);
+    }
+
+    private void givenUnfoldTransitionEnabled(boolean enabled) {
+        when(mResources.getBoolean(config_unfoldTransitionEnabled)).thenReturn(enabled);
+    }
+
+    private void givenAnimationsEnabled(boolean enabled) {
+        ValueAnimator.setDurationScale(enabled ? 1.0f : 0.0f);
+    }
+
+    private void givenShellTransitionsEnabled(boolean enabled) {
+        when(mTransitionController.isShellTransitionsEnabled()).thenReturn(enabled);
+    }
+
+    private void givenDisplayContentHasContent(boolean hasContent) {
+        when(mDisplayContent.getLastHasContent()).thenReturn(hasContent);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index c8fc6b8..d84620b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -32,6 +32,7 @@
 
 import com.android.server.testutils.StubTransaction;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -52,6 +53,11 @@
         SurfaceSyncGroup.setTransactionFactory(StubTransaction::new);
     }
 
+    @After
+    public void tearDown() {
+        SurfaceSyncGroup.setTransactionFactory(SurfaceControl.Transaction::new);
+    }
+
     @Test
     public void testSyncOne() throws InterruptedException {
         final CountDownLatch finishedLatch = new CountDownLatch(1);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/DeviceStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/DeviceStateHandler.java
new file mode 100644
index 0000000..6605449
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/DeviceStateHandler.java
@@ -0,0 +1,279 @@
+/**
+ * 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 android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_DISABLED;
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.EventLogger;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages device state events which require pausing SoundTrigger recognition
+ *
+ * @hide
+ */
+public class DeviceStateHandler implements PhoneCallStateHandler.Callback {
+
+    public static final long CALL_INACTIVE_MSG_DELAY_MS = 1000;
+
+    public interface DeviceStateListener {
+        void onSoundTriggerDeviceStateUpdate(SoundTriggerDeviceState state);
+    }
+
+    public enum SoundTriggerDeviceState {
+        DISABLE, // The device state requires all SoundTrigger sessions are disabled
+        CRITICAL, // The device state requires all non-critical SoundTrigger sessions are disabled
+        ENABLE // The device state permits all SoundTrigger sessions
+    }
+
+    private final Object mLock = new Object();
+
+    private final EventLogger mEventLogger;
+
+    @GuardedBy("mLock")
+    SoundTriggerDeviceState mSoundTriggerDeviceState = SoundTriggerDeviceState.ENABLE;
+
+    // Individual components of the SoundTriggerDeviceState
+    @GuardedBy("mLock")
+    private int mSoundTriggerPowerSaveMode = SOUND_TRIGGER_MODE_ALL_ENABLED;
+
+    @GuardedBy("mLock")
+    private boolean mIsPhoneCallOngoing = false;
+
+    // There can only be one pending notify at any given time.
+    // If any phone state change comes in between, we will cancel the previous pending
+    // task.
+    @GuardedBy("mLock")
+    private NotificationTask mPhoneStateChangePendingNotify = null;
+
+    private Set<DeviceStateListener> mCallbackSet = ConcurrentHashMap.newKeySet(4);
+
+    private final Executor mDelayedNotificationExecutor = Executors.newSingleThreadExecutor();
+
+    private final Executor mCallbackExecutor;
+
+    public void onPowerModeChanged(int soundTriggerPowerSaveMode) {
+        mEventLogger.enqueue(new SoundTriggerPowerEvent(soundTriggerPowerSaveMode));
+        synchronized (mLock) {
+            if (soundTriggerPowerSaveMode == mSoundTriggerPowerSaveMode) {
+                // No state change, nothing to do
+                return;
+            }
+            mSoundTriggerPowerSaveMode = soundTriggerPowerSaveMode;
+            evaluateStateChange();
+        }
+    }
+
+    @Override
+    public void onPhoneCallStateChanged(boolean isInPhoneCall) {
+        mEventLogger.enqueue(new PhoneCallEvent(isInPhoneCall));
+        synchronized (mLock) {
+            if (mIsPhoneCallOngoing == isInPhoneCall) {
+                // no change, nothing to do
+                return;
+            }
+            // Clear any pending notification
+            if (mPhoneStateChangePendingNotify != null) {
+                mPhoneStateChangePendingNotify.cancel();
+                mPhoneStateChangePendingNotify = null;
+            }
+            mIsPhoneCallOngoing = isInPhoneCall;
+            if (!mIsPhoneCallOngoing) {
+                // State has changed from call to no call, delay notification
+                mPhoneStateChangePendingNotify = new NotificationTask(
+                        new Runnable() {
+                            @Override
+                            public void run() {
+                                synchronized (mLock) {
+                                    if (mPhoneStateChangePendingNotify != null &&
+                                            mPhoneStateChangePendingNotify.runnableEquals(this)) {
+
+                                        mPhoneStateChangePendingNotify = null;
+                                        evaluateStateChange();
+                                    }
+                                }
+                            }
+                        },
+                        CALL_INACTIVE_MSG_DELAY_MS);
+                mDelayedNotificationExecutor.execute(mPhoneStateChangePendingNotify);
+            } else {
+                evaluateStateChange();
+            }
+        }
+    }
+
+    /** Note, we expect initial callbacks immediately following construction */
+    public DeviceStateHandler(Executor callbackExecutor, EventLogger eventLogger) {
+        mCallbackExecutor = Objects.requireNonNull(callbackExecutor);
+        mEventLogger = Objects.requireNonNull(eventLogger);
+    }
+
+    public SoundTriggerDeviceState getDeviceState() {
+        synchronized (mLock) {
+            return mSoundTriggerDeviceState;
+        }
+    }
+
+    public void registerListener(DeviceStateListener callback) {
+        final var state = getDeviceState();
+        mCallbackExecutor.execute(
+                () -> callback.onSoundTriggerDeviceStateUpdate(state));
+        mCallbackSet.add(callback);
+    }
+
+    public void unregisterListener(DeviceStateListener callback) {
+        mCallbackSet.remove(callback);
+    }
+
+    void dump(PrintWriter pw) {
+        synchronized (mLock) {
+            pw.println("DeviceState: " + mSoundTriggerDeviceState.name());
+            pw.println("PhoneState: " + mIsPhoneCallOngoing);
+            pw.println("PowerSaveMode: " + mSoundTriggerPowerSaveMode);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void evaluateStateChange() {
+        // We should wait until any pending delays are complete to update.
+        // We will eventually get called by the notification task, or something which
+        // cancels it.
+        // Additionally, if there isn't a state change, there is nothing to update.
+        SoundTriggerDeviceState newState = computeState();
+        if (mPhoneStateChangePendingNotify != null || mSoundTriggerDeviceState == newState) {
+            return;
+        }
+
+        mSoundTriggerDeviceState = newState;
+        mEventLogger.enqueue(new DeviceStateEvent(mSoundTriggerDeviceState));
+        final var state = mSoundTriggerDeviceState;
+        for (var callback : mCallbackSet) {
+            mCallbackExecutor.execute(
+                    () -> callback.onSoundTriggerDeviceStateUpdate(state));
+        }
+    }
+
+    @GuardedBy("mLock")
+    private SoundTriggerDeviceState computeState() {
+        if (mIsPhoneCallOngoing) {
+            return SoundTriggerDeviceState.DISABLE;
+        }
+        return switch (mSoundTriggerPowerSaveMode) {
+            case SOUND_TRIGGER_MODE_ALL_ENABLED -> SoundTriggerDeviceState.ENABLE;
+            case SOUND_TRIGGER_MODE_CRITICAL_ONLY -> SoundTriggerDeviceState.CRITICAL;
+            case SOUND_TRIGGER_MODE_ALL_DISABLED -> SoundTriggerDeviceState.DISABLE;
+            default -> throw new IllegalStateException(
+                    "Received unexpected power state code" + mSoundTriggerPowerSaveMode);
+        };
+    }
+
+    /**
+     * One-shot, cancellable task which runs after a delay. Run must only be called once, from a
+     * single thread. Cancel can be called from any other thread.
+     */
+    private static class NotificationTask implements Runnable {
+        private final Runnable mRunnable;
+        private final long mWaitInMillis;
+
+        private final CountDownLatch mCancelLatch = new CountDownLatch(1);
+
+        NotificationTask(Runnable r, long waitInMillis) {
+            mRunnable = r;
+            mWaitInMillis = waitInMillis;
+        }
+
+        void cancel() {
+            mCancelLatch.countDown();
+        }
+
+        // Used for determining task equality.
+        boolean runnableEquals(Runnable runnable) {
+            return mRunnable == runnable;
+        }
+
+        public void run() {
+            try {
+                if (!mCancelLatch.await(mWaitInMillis, TimeUnit.MILLISECONDS)) {
+                    mRunnable.run();
+                }
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new AssertionError("Unexpected InterruptedException", e);
+            }
+        }
+    }
+
+    private static class PhoneCallEvent extends EventLogger.Event {
+        final boolean mIsInPhoneCall;
+
+        PhoneCallEvent(boolean isInPhoneCall) {
+            mIsInPhoneCall = isInPhoneCall;
+        }
+
+        @Override
+        public String eventToString() {
+            return "PhoneCallChange - inPhoneCall: " + mIsInPhoneCall;
+        }
+    }
+
+    private static class SoundTriggerPowerEvent extends EventLogger.Event {
+        final int mSoundTriggerPowerState;
+
+        SoundTriggerPowerEvent(int soundTriggerPowerState) {
+            mSoundTriggerPowerState = soundTriggerPowerState;
+        }
+
+        @Override
+        public String eventToString() {
+            return "SoundTriggerPowerChange: " + stateToString();
+        }
+
+        private String stateToString() {
+            return switch (mSoundTriggerPowerState) {
+                case SOUND_TRIGGER_MODE_ALL_ENABLED -> "All enabled";
+                case SOUND_TRIGGER_MODE_CRITICAL_ONLY -> "Critical only";
+                case SOUND_TRIGGER_MODE_ALL_DISABLED -> "All disabled";
+                default -> "Unknown power state: " + mSoundTriggerPowerState;
+            };
+        }
+    }
+
+    private static class DeviceStateEvent extends EventLogger.Event {
+        final SoundTriggerDeviceState mSoundTriggerDeviceState;
+
+        DeviceStateEvent(SoundTriggerDeviceState soundTriggerDeviceState) {
+            mSoundTriggerDeviceState = soundTriggerDeviceState;
+        }
+
+        @Override
+        public String eventToString() {
+            return "DeviceStateChange: " + mSoundTriggerDeviceState.name();
+        }
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
new file mode 100644
index 0000000..8773cab
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
@@ -0,0 +1,158 @@
+/**
+ * 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.telephony.Annotation;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Handles monitoring telephony call state across active subscriptions.
+ *
+ * @hide
+ */
+public class PhoneCallStateHandler {
+
+    public interface Callback {
+        void onPhoneCallStateChanged(boolean isInPhoneCall);
+    }
+
+    private final Object mLock = new Object();
+
+    // Actually never contended due to executor.
+    @GuardedBy("mLock")
+    private final List<MyCallStateListener> mListenerList = new ArrayList<>();
+
+    private final AtomicBoolean mIsPhoneCallOngoing = new AtomicBoolean(false);
+
+    private final SubscriptionManager mSubscriptionManager;
+    private final TelephonyManager mTelephonyManager;
+    private final Callback mCallback;
+
+    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
+    public PhoneCallStateHandler(
+            SubscriptionManager subscriptionManager,
+            TelephonyManager telephonyManager,
+            Callback callback) {
+        mSubscriptionManager = Objects.requireNonNull(subscriptionManager);
+        mTelephonyManager = Objects.requireNonNull(telephonyManager);
+        mCallback = Objects.requireNonNull(callback);
+        mSubscriptionManager.addOnSubscriptionsChangedListener(
+                mExecutor,
+                new SubscriptionManager.OnSubscriptionsChangedListener() {
+                    @Override
+                    public void onSubscriptionsChanged() {
+                        updateTelephonyListeners();
+                    }
+
+                    @Override
+                    public void onAddListenerFailed() {
+                        Slog.wtf(
+                                "SoundTriggerPhoneCallStateHandler",
+                                "Failed to add a telephony listener");
+                    }
+                });
+    }
+
+    private final class MyCallStateListener extends TelephonyCallback
+            implements TelephonyCallback.CallStateListener {
+
+        final TelephonyManager mTelephonyManagerForSubId;
+
+        // Manager corresponding to the sub-id
+        MyCallStateListener(TelephonyManager telephonyManager) {
+            mTelephonyManagerForSubId = telephonyManager;
+        }
+
+        void cleanup() {
+            mExecutor.execute(() -> mTelephonyManagerForSubId.unregisterTelephonyCallback(this));
+        }
+
+        @Override
+        public void onCallStateChanged(int unused) {
+            updateCallStatus();
+        }
+    }
+
+    /** Compute the current call status, and dispatch callback if it has changed. */
+    private void updateCallStatus() {
+        boolean callStatus = checkCallStatus();
+        if (mIsPhoneCallOngoing.compareAndSet(!callStatus, callStatus)) {
+            mCallback.onPhoneCallStateChanged(callStatus);
+        }
+    }
+
+    /**
+     * Synchronously query the current telephony call state across all subscriptions
+     *
+     * @return - {@code true} if in call, {@code false} if not in call.
+     */
+    private boolean checkCallStatus() {
+        List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList();
+        if (infoList == null) return false;
+        return infoList.stream()
+                .filter(s -> (s.getSubscriptionId() != SubscriptionManager.INVALID_SUBSCRIPTION_ID))
+                .anyMatch(s -> isCallOngoingFromState(
+                                        mTelephonyManager
+                                                .createForSubscriptionId(s.getSubscriptionId())
+                                                .getCallStateForSubscription()));
+    }
+
+    private void updateTelephonyListeners() {
+        synchronized (mLock) {
+            for (var listener : mListenerList) {
+                listener.cleanup();
+            }
+            mListenerList.clear();
+            List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList();
+            if (infoList == null) return;
+            infoList.stream()
+                    .filter(s -> s.getSubscriptionId()
+                                            != SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+                    .map(s -> mTelephonyManager.createForSubscriptionId(s.getSubscriptionId()))
+                    .forEach(manager -> {
+                        synchronized (mLock) {
+                            var listener = new MyCallStateListener(manager);
+                            mListenerList.add(listener);
+                            manager.registerTelephonyCallback(mExecutor, listener);
+                        }
+                    });
+        }
+    }
+
+    private static boolean isCallOngoingFromState(@Annotation.CallState int callState) {
+        return switch (callState) {
+            case TelephonyManager.CALL_STATE_IDLE, TelephonyManager.CALL_STATE_RINGING -> false;
+            case TelephonyManager.CALL_STATE_OFFHOOK -> true;
+            default -> throw new IllegalStateException(
+                    "Received unexpected call state from Telephony Manager: " + callState);
+        };
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 255db1e..b4066ab 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -16,15 +16,12 @@
 
 package com.android.server.soundtrigger;
 
+import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState;
 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;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger;
@@ -45,11 +42,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.PowerManager;
-import android.os.PowerManager.SoundTriggerPowerSaveMode;
 import android.os.RemoteException;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -99,37 +92,20 @@
     private SoundTriggerModule mModule;
     private final Object mLock = new Object();
     private final Context mContext;
-    private final TelephonyManager mTelephonyManager;
-    private final PhoneStateListener mPhoneStateListener;
-    private final PowerManager mPowerManager;
 
     // The SoundTriggerManager layer handles multiple recognition models of type generic and
     // keyphrase. We store the ModelData here in a hashmap.
-    private final HashMap<UUID, ModelData> mModelDataMap;
+    private final HashMap<UUID, ModelData> mModelDataMap = new HashMap<>();
 
     // An index of keyphrase sound models so that we can reach them easily. We support indexing
     // keyphrase sound models with a keyphrase ID. Sound model with the same keyphrase ID will
     // replace an existing model, thus there is a 1:1 mapping from keyphrase ID to a voice
     // sound model.
-    private HashMap<Integer, UUID> mKeyphraseUuidMap;
-
-    private boolean mCallActive = false;
-    private @SoundTriggerPowerSaveMode int mSoundTriggerPowerSaveMode =
-            PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
+    private final HashMap<Integer, UUID> mKeyphraseUuidMap = new HashMap<>();
 
     // Whether ANY recognition (keyphrase or generic) has been requested.
     private boolean mRecognitionRequested = false;
 
-    private PowerSaveModeListener mPowerSaveModeListener;
-
-
-    // Handler to process call state changes will delay to allow time for the audio
-    // and sound trigger HALs to process the end of call notifications
-    // before we re enable pending recognition requests.
-    private final Handler mHandler;
-    private static final int MSG_CALL_STATE_CHANGED = 0;
-    private static final int CALL_INACTIVE_MSG_DELAY_MS = 1000;
-
     // TODO(b/269366605) Temporary solution to query correct moduleProperties
     private final int mModuleId;
     private final Function<SoundTrigger.StatusListener, SoundTriggerModule> mModuleProvider;
@@ -139,16 +115,15 @@
     @GuardedBy("mLock")
     private boolean mIsDetached = false;
 
+    @GuardedBy("mLock")
+    private SoundTriggerDeviceState mDeviceState = SoundTriggerDeviceState.DISABLE;
+
     SoundTriggerHelper(Context context, EventLogger eventLogger,
             @NonNull Function<SoundTrigger.StatusListener, SoundTriggerModule> moduleProvider,
             int moduleId,
             @NonNull Supplier<List<ModuleProperties>> modulePropertiesProvider) {
         mModuleId = moduleId;
         mContext = context;
-        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        mModelDataMap = new HashMap<UUID, ModelData>();
-        mKeyphraseUuidMap = new HashMap<Integer, UUID>();
         mModuleProvider = moduleProvider;
         mEventLogger = eventLogger;
         mModulePropertiesProvider = modulePropertiesProvider;
@@ -157,31 +132,6 @@
         } else {
             mModule = mModuleProvider.apply(this);
         }
-        Looper looper = Looper.myLooper();
-        if (looper == null) {
-            looper = Looper.getMainLooper();
-        }
-        mPhoneStateListener = new MyCallStateListener(looper);
-        if (looper != null) {
-            mHandler = new Handler(looper) {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch (msg.what) {
-                        case MSG_CALL_STATE_CHANGED:
-                            synchronized (mLock) {
-                                onCallStateChangedLocked(
-                                        TelephonyManager.CALL_STATE_OFFHOOK == msg.arg1);
-                            }
-                            break;
-                        default:
-                            Slog.e(TAG, "unknown message in handler:" + msg.what);
-                            break;
-                    }
-                }
-            };
-        } else {
-            mHandler = null;
-        }
     }
 
     /**
@@ -373,7 +323,6 @@
             modelData.setSoundModel(soundModel);
 
             if (!isRecognitionAllowedByDeviceState(modelData)) {
-                initializeDeviceStateListeners();
                 return STATUS_OK;
             }
 
@@ -497,11 +446,6 @@
             modelData.setLoaded();
             modelData.clearCallback();
             modelData.setRecognitionConfig(null);
-
-            if (!computeRecognitionRequestedLocked()) {
-                internalClearGlobalStateLocked();
-            }
-
             return status;
         }
     }
@@ -638,6 +582,17 @@
         }
     }
 
+    public void onDeviceStateChanged(SoundTriggerDeviceState state) {
+        synchronized (mLock) {
+            if (mIsDetached || mDeviceState == state) {
+                // Nothing to update
+                return;
+            }
+            mDeviceState = state;
+            updateAllRecognitionsLocked();
+        }
+    }
+
     public int getGenericModelState(UUID modelId) {
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_get_generic_model_state", 1);
@@ -880,25 +835,6 @@
         }
     }
 
-    private void onCallStateChangedLocked(boolean callActive) {
-        if (mCallActive == callActive) {
-            // We consider multiple call states as being active
-            // so we check if something really changed or not here.
-            return;
-        }
-        mCallActive = callActive;
-        updateAllRecognitionsLocked();
-    }
-
-    private void onPowerSaveModeChangedLocked(
-            @SoundTriggerPowerSaveMode int soundTriggerPowerSaveMode) {
-        if (mSoundTriggerPowerSaveMode == soundTriggerPowerSaveMode) {
-            return;
-        }
-        mSoundTriggerPowerSaveMode = soundTriggerPowerSaveMode;
-        updateAllRecognitionsLocked();
-    }
-
     private void onModelUnloadedLocked(int modelHandle) {
         ModelData modelData = getModelDataForLocked(modelHandle);
         if (modelData != null) {
@@ -1011,10 +947,6 @@
                 return status;
             }
             status = startRecognitionLocked(model, notifyClientOnError);
-            // Initialize power save, call active state monitoring logic.
-            if (status == STATUS_OK) {
-                initializeDeviceStateListeners();
-            }
             return status;
         } else {
             return stopRecognitionLocked(model, notifyClientOnError);
@@ -1040,7 +972,6 @@
             }
         } finally {
             internalClearModelStateLocked();
-            internalClearGlobalStateLocked();
             if (mModule != null) {
                 mModule.detach();
                 try {
@@ -1054,24 +985,6 @@
         }
     }
 
-    // internalClearGlobalStateLocked() cleans up the telephony and power save listeners.
-    private void internalClearGlobalStateLocked() {
-        // Unregister from call state changes.
-        final long token = Binder.clearCallingIdentity();
-        try {
-            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-
-        // Unregister from power save mode changes.
-        if (mPowerSaveModeListener != null) {
-            mContext.unregisterReceiver(mPowerSaveModeListener);
-            mPowerSaveModeListener = null;
-        }
-        mRecognitionRequested = false;
-    }
-
     // Clears state for all models (generic and keyphrase).
     private void internalClearModelStateLocked() {
         for (ModelData modelData : mModelDataMap.values()) {
@@ -1079,67 +992,6 @@
         }
     }
 
-    class MyCallStateListener extends PhoneStateListener {
-        MyCallStateListener(@NonNull Looper looper) {
-            super(Objects.requireNonNull(looper));
-        }
-
-        @Override
-        public void onCallStateChanged(int state, String arg1) {
-
-            if (mHandler != null) {
-                synchronized (mLock) {
-                    mHandler.removeMessages(MSG_CALL_STATE_CHANGED);
-                    Message msg = mHandler.obtainMessage(MSG_CALL_STATE_CHANGED, state, 0);
-                    mHandler.sendMessageDelayed(
-                            msg, (TelephonyManager.CALL_STATE_OFFHOOK == state) ? 0
-                                    : CALL_INACTIVE_MSG_DELAY_MS);
-                }
-            }
-        }
-    }
-
-    class PowerSaveModeListener extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
-                return;
-            }
-            @SoundTriggerPowerSaveMode int soundTriggerPowerSaveMode =
-                    mPowerManager.getSoundTriggerPowerSaveMode();
-            synchronized (mLock) {
-                onPowerSaveModeChangedLocked(soundTriggerPowerSaveMode);
-            }
-        }
-    }
-
-    private void initializeDeviceStateListeners() {
-        if (mRecognitionRequested) {
-            return;
-        }
-        final long token = Binder.clearCallingIdentity();
-        try {
-            // Get the current call state synchronously for the first recognition.
-            mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
-
-            // Register for call state changes when the first call to start recognition occurs.
-            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
-
-            // Register for power saver mode changes when the first call to start recognition
-            // occurs.
-            if (mPowerSaveModeListener == null) {
-                mPowerSaveModeListener = new PowerSaveModeListener();
-                mContext.registerReceiver(mPowerSaveModeListener,
-                        new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
-            }
-            mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode();
-
-            mRecognitionRequested = true;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
     /**
      * Stops and unloads all models. This is intended as a clean-up call with the expectation that
      * this instance is not used after.
@@ -1153,7 +1005,6 @@
                 forceStopAndUnloadModelLocked(model, null);
             }
             mModelDataMap.clear();
-            internalClearGlobalStateLocked();
             if (mModule != null) {
                 mModule.detach();
                 mModule = null;
@@ -1305,28 +1156,14 @@
      * @param modelData Model data to be used for recognition
      * @return True if recognition is allowed to run at this time. False if not.
      */
+    @GuardedBy("mLock")
     private boolean isRecognitionAllowedByDeviceState(ModelData modelData) {
-        // if mRecognitionRequested is false, call and power state listeners are not registered so
-        // we read current state directly from services
-        if (!mRecognitionRequested) {
-            mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
-            mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode();
-        }
-
-        return !mCallActive && isRecognitionAllowedByPowerState(
-                modelData);
-    }
-
-    /**
-     * Helper function to validate if a recognition should run based on the current power state
-     *
-     * @param modelData Model data to be used for recognition
-     * @return True if device state allows recognition to run, false if not.
-     */
-    private boolean isRecognitionAllowedByPowerState(ModelData modelData) {
-        return mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED
-                || (mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY
-                && modelData.shouldRunInBatterySaverMode());
+        return switch (mDeviceState) {
+            case DISABLE -> false;
+            case CRITICAL -> modelData.shouldRunInBatterySaverMode();
+            case ENABLE -> true;
+            default -> throw new AssertionError("Enum changed between compile and runtime");
+        };
     }
 
     // A single routine that implements the start recognition logic for both generic and keyphrase
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 913535e..3151781 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -35,14 +35,18 @@
 import static com.android.server.utils.EventLogger.Event.ALOGW;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.server.soundtrigger.DeviceStateHandler.DeviceStateListener;
+import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState;
 
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.PermissionChecker;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
@@ -85,6 +89,8 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseArray;
@@ -112,6 +118,8 @@
 import java.util.TreeMap;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingDeque;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -131,8 +139,8 @@
     private static final boolean DEBUG = true;
     private static final int SESSION_MAX_EVENT_SIZE = 128;
 
-    final Context mContext;
-    private Object mLock;
+    private final Context mContext;
+    private final Object mLock = new Object();
     private final SoundTriggerServiceStub mServiceStub;
     private final LocalSoundTriggerService mLocalSoundTriggerService;
 
@@ -140,6 +148,7 @@
     private SoundTriggerDbHelper mDbHelper;
 
     private final EventLogger mServiceEventLogger = new EventLogger(256, "Service");
+    private final EventLogger mDeviceEventLogger = new EventLogger(256, "Device Event");
 
     private final Set<EventLogger> mSessionEventLoggers = ConcurrentHashMap.newKeySet(4);
     private final Deque<EventLogger> mDetachedSessionEventLoggers = new LinkedBlockingDeque<>(4);
@@ -223,13 +232,18 @@
     @GuardedBy("mLock")
     private final ArrayMap<String, NumOps> mNumOpsPerPackage = new ArrayMap<>();
 
+    private final DeviceStateHandler mDeviceStateHandler;
+    private final Executor mDeviceStateHandlerExecutor = Executors.newSingleThreadExecutor();
+    private PhoneCallStateHandler mPhoneCallStateHandler;
+
     public SoundTriggerService(Context context) {
         super(context);
         mContext = context;
         mServiceStub = new SoundTriggerServiceStub();
         mLocalSoundTriggerService = new LocalSoundTriggerService(context);
-        mLock = new Object();
         mSoundModelStatTracker = new SoundModelStatTracker();
+        mDeviceStateHandler = new DeviceStateHandler(mDeviceStateHandlerExecutor,
+                mDeviceEventLogger);
     }
 
     @Override
@@ -243,6 +257,29 @@
         Slog.d(TAG, "onBootPhase: " + phase + " : " + isSafeMode());
         if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
             mDbHelper = new SoundTriggerDbHelper(mContext);
+            final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+            // Hook up power state listener
+            mContext.registerReceiver(
+                    new BroadcastReceiver() {
+                        @Override
+                        public void onReceive(Context context, Intent intent) {
+                            if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED
+                                    .equals(intent.getAction())) {
+                                return;
+                            }
+                            mDeviceStateHandler.onPowerModeChanged(
+                                    powerManager.getSoundTriggerPowerSaveMode());
+                        }
+                    }, new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
+            // Initialize the initial power state
+            // Do so after registering the listener so we ensure that we don't drop any events
+            mDeviceStateHandler.onPowerModeChanged(powerManager.getSoundTriggerPowerSaveMode());
+
+            // PhoneCallStateHandler initializes the original call state
+            mPhoneCallStateHandler = new PhoneCallStateHandler(
+                      mContext.getSystemService(SubscriptionManager.class),
+                      mContext.getSystemService(TelephonyManager.class),
+                      mDeviceStateHandler);
         }
         mMiddlewareService = ISoundTriggerMiddlewareService.Stub.asInterface(
                 ServiceManager.waitForService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE));
@@ -380,6 +417,9 @@
             // Event loggers
             pw.println("##Service-Wide logs:");
             mServiceEventLogger.dump(pw, /* indent = */ "  ");
+            pw.println("\n##Device state logs:");
+            mDeviceStateHandler.dump(pw);
+            mDeviceEventLogger.dump(pw, /* indent = */ "  ");
 
             pw.println("\n##Active Session dumps:\n");
             for (var sessionLogger : mSessionEventLoggers) {
@@ -403,6 +443,7 @@
 
     class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
         private final SoundTriggerHelper mSoundTriggerHelper;
+        private final DeviceStateListener mListener;
         // Used to detect client death.
         private final IBinder mClient;
         private final Identity mOriginatorIdentity;
@@ -424,6 +465,9 @@
             } catch (RemoteException e) {
                 clientDied();
             }
+            mListener = (SoundTriggerDeviceState state)
+                    -> mSoundTriggerHelper.onDeviceStateChanged(state);
+            mDeviceStateHandler.registerListener(mListener);
         }
 
         @Override
@@ -874,6 +918,7 @@
         }
 
         private void detach() {
+            mDeviceStateHandler.unregisterListener(mListener);
             mSoundTriggerHelper.detach();
             detachSessionLogger(mEventLogger);
         }
@@ -890,7 +935,8 @@
         private void enforceDetectionPermissions(ComponentName detectionService) {
             PackageManager packageManager = mContext.getPackageManager();
             String packageName = detectionService.getPackageName();
-            if (packageManager.checkPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName)
+            if (packageManager.checkPermission(
+                        Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName)
                     != PackageManager.PERMISSION_GRANTED) {
                 throw new SecurityException(detectionService.getPackageName() + " does not have"
                         + " permission " + Manifest.permission.CAPTURE_AUDIO_HOTWORD);
@@ -1576,6 +1622,7 @@
             private final @NonNull IBinder mClient;
             private final EventLogger mEventLogger;
             private final Identity mOriginatorIdentity;
+            private final @NonNull DeviceStateListener mListener;
 
             private final SparseArray<UUID> mModelUuid = new SparseArray<>(1);
 
@@ -1594,6 +1641,9 @@
                 } catch (RemoteException e) {
                     clientDied();
                 }
+                mListener = (SoundTriggerDeviceState state)
+                        -> mSoundTriggerHelper.onDeviceStateChanged(state);
+                mDeviceStateHandler.registerListener(mListener);
             }
 
             @Override
@@ -1662,6 +1712,7 @@
             private void detachInternal() {
                 mEventLogger.enqueue(new SessionEvent(Type.DETACH, null));
                 detachSessionLogger(mEventLogger);
+                mDeviceStateHandler.unregisterListener(mListener);
                 mSoundTriggerHelper.detach();
             }
         }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/AidlUtil.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/AidlUtil.java
index f3457f5..56a159e 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/AidlUtil.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/AidlUtil.java
@@ -21,6 +21,8 @@
 import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.SoundModelType;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 
 /**
  * Utilities for working with sound trigger related AIDL generated types.
@@ -49,23 +51,29 @@
 
     /**
      * Creates a new generic abort event.
+     *
      * @return The new event.
      */
-    static RecognitionEvent newAbortEvent() {
-        RecognitionEvent event = newEmptyRecognitionEvent();
-        event.type = SoundModelType.GENERIC;
-        event.status = RecognitionStatus.ABORTED;
-        return event;
+    static RecognitionEventSys newAbortEvent() {
+        RecognitionEvent recognitionEvent = newEmptyRecognitionEvent();
+        recognitionEvent.type = SoundModelType.GENERIC;
+        recognitionEvent.status = RecognitionStatus.ABORTED;
+        RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+        recognitionEventSys.recognitionEvent = recognitionEvent;
+        return recognitionEventSys;
     }
 
     /**
      * Creates a new generic phrase event.
+     *
      * @return The new event.
      */
-    static PhraseRecognitionEvent newAbortPhraseEvent() {
-        PhraseRecognitionEvent event = newEmptyPhraseRecognitionEvent();
-        event.common.type = SoundModelType.KEYPHRASE;
-        event.common.status = RecognitionStatus.ABORTED;
-        return event;
+    static PhraseRecognitionEventSys newAbortPhraseEvent() {
+        PhraseRecognitionEvent recognitionEvent = newEmptyPhraseRecognitionEvent();
+        recognitionEvent.common.type = SoundModelType.KEYPHRASE;
+        recognitionEvent.common.status = RecognitionStatus.ABORTED;
+        PhraseRecognitionEventSys phraseRecognitionEventSys = new PhraseRecognitionEventSys();
+        phraseRecognitionEventSys.phraseRecognitionEvent = recognitionEvent;
+        return phraseRecognitionEventSys;
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerHal.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerHal.java
index 75206e6..6f4a946 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerHal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerHal.java
@@ -20,12 +20,12 @@
 import android.hardware.soundtrigger3.ISoundTriggerHwCallback;
 import android.hardware.soundtrigger3.ISoundTriggerHwGlobalCallback;
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.IBinder;
 
 /**
@@ -173,14 +173,19 @@
      */
     interface ModelCallback {
         /**
-         * @see ISoundTriggerHwCallback#recognitionCallback(int, RecognitionEvent)
+         * Decorated callback of
+         * {@link ISoundTriggerHwCallback#recognitionCallback(int, RecognitionEvent)} where
+         * {@link RecognitionEventSys} is decorating the returned {@link RecognitionEvent}
          */
-        void recognitionCallback(int modelHandle, RecognitionEvent event);
+        void recognitionCallback(int modelHandle, RecognitionEventSys event);
 
         /**
-         * @see ISoundTriggerHwCallback#phraseRecognitionCallback(int, PhraseRecognitionEvent)
+         * Decorated callback of
+         * {@link ISoundTriggerHwCallback#phraseRecognitionCallback(int, PhraseRecognitionEvent)}
+         * where {@link PhraseRecognitionEventSys} is decorating the returned
+         * {@link PhraseRecognitionEvent}
          */
-        void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEvent event);
+        void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEventSys event);
 
         /**
          * @see ISoundTriggerHwCallback#modelUnloaded(int)
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
index 8c7cabe..d8ef2b6 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
@@ -19,14 +19,14 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.SoundModelType;
 import android.media.soundtrigger.Status;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.IBinder;
 
 import java.util.HashSet;
@@ -238,13 +238,13 @@
         }
 
         @Override
-        public void recognitionCallback(int modelHandle, RecognitionEvent event) {
+        public void recognitionCallback(int modelHandle, RecognitionEventSys event) {
             synchronized (mActiveModels) {
                 if (!mActiveModels.contains(modelHandle)) {
                     // Discard the event.
                     return;
                 }
-                if (!event.recognitionStillActive) {
+                if (!event.recognitionEvent.recognitionStillActive) {
                     mActiveModels.remove(modelHandle);
                 }
                 // A recognition event must be the last one for its model, unless it indicates that
@@ -255,13 +255,13 @@
         }
 
         @Override
-        public void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEvent event) {
+        public void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEventSys event) {
             synchronized (mActiveModels) {
                 if (!mActiveModels.contains(modelHandle)) {
                     // Discard the event.
                     return;
                 }
-                if (!event.common.recognitionStillActive) {
+                if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
                     mActiveModels.remove(modelHandle);
                 }
                 // A recognition event must be the last one for its model, unless it indicates that
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
index 24741e1..bac2466 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
@@ -17,14 +17,14 @@
 package com.android.server.soundtrigger_middleware;
 
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.DeadObjectException;
 import android.os.IBinder;
 import android.util.Log;
@@ -253,7 +253,7 @@
         }
 
         @Override
-        public void recognitionCallback(int model, RecognitionEvent event) {
+        public void recognitionCallback(int model, RecognitionEventSys event) {
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(model);
                 if (state == null || state == ModelState.INACTIVE) {
@@ -261,15 +261,16 @@
                     reboot();
                     return;
                 }
-                if (event.recognitionStillActive && event.status != RecognitionStatus.SUCCESS
-                        && event.status != RecognitionStatus.FORCED) {
+                if (event.recognitionEvent.recognitionStillActive
+                        && event.recognitionEvent.status != RecognitionStatus.SUCCESS
+                        && event.recognitionEvent.status != RecognitionStatus.FORCED) {
                     Log.wtfStack(TAG,
                             "recognitionStillActive is only allowed when the recognition status "
                                     + "is SUCCESS");
                     reboot();
                     return;
                 }
-                if (!event.recognitionStillActive) {
+                if (!event.recognitionEvent.recognitionStillActive) {
                     mModelStates.replace(model, ModelState.INACTIVE);
                 }
             }
@@ -278,7 +279,7 @@
         }
 
         @Override
-        public void phraseRecognitionCallback(int model, PhraseRecognitionEvent event) {
+        public void phraseRecognitionCallback(int model, PhraseRecognitionEventSys event) {
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(model);
                 if (state == null || state == ModelState.INACTIVE) {
@@ -286,16 +287,16 @@
                     reboot();
                     return;
                 }
-                if (event.common.recognitionStillActive
-                        && event.common.status != RecognitionStatus.SUCCESS
-                        && event.common.status != RecognitionStatus.FORCED) {
+                if (event.phraseRecognitionEvent.common.recognitionStillActive
+                        && event.phraseRecognitionEvent.common.status != RecognitionStatus.SUCCESS
+                        && event.phraseRecognitionEvent.common.status != RecognitionStatus.FORCED) {
                     Log.wtfStack(TAG,
                             "recognitionStillActive is only allowed when the recognition status "
                                     + "is SUCCESS");
                     reboot();
                     return;
                 }
-                if (!event.common.recognitionStillActive) {
+                if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
                     mModelStates.replace(model, ModelState.INACTIVE);
                 }
             }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
index c67bdd7..df2e9b4 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -25,9 +25,12 @@
 import android.media.soundtrigger.RecognitionConfig;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.IBinder;
 import android.os.IHwBinder;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.system.OsConstants;
 import android.util.Log;
 
@@ -570,16 +573,20 @@
         public void recognitionCallback_2_1(
                 android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event,
                 int cookie) {
-            mDelegate.recognitionCallback(event.header.model,
-                    ConversionUtil.hidl2aidlRecognitionEvent(event));
+            RecognitionEventSys eventSys = new RecognitionEventSys();
+            eventSys.recognitionEvent = ConversionUtil.hidl2aidlRecognitionEvent(event);
+            eventSys.halEventReceivedMillis = SystemClock.elapsedRealtime();
+            mDelegate.recognitionCallback(event.header.model, eventSys);
         }
 
         @Override
         public void phraseRecognitionCallback_2_1(
                 android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
                 int cookie) {
-            mDelegate.phraseRecognitionCallback(event.common.header.model,
-                    ConversionUtil.hidl2aidlPhraseRecognitionEvent(event));
+            PhraseRecognitionEventSys eventSys = new PhraseRecognitionEventSys();
+            eventSys.phraseRecognitionEvent = ConversionUtil.hidl2aidlPhraseRecognitionEvent(event);
+            eventSys.halEventReceivedMillis = SystemClock.elapsedRealtime();
+            mDelegate.phraseRecognitionCallback(event.common.header.model, eventSys);
         }
 
         @Override
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
index 8bb5eb1..b1165bb 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
@@ -29,9 +29,12 @@
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
+import android.os.SystemClock;
 
 public class SoundTriggerHw3Compat implements ISoundTriggerHal {
     private final @NonNull ISoundTriggerHw mDriver;
@@ -244,14 +247,20 @@
         public void phraseRecognitionCallback(int model, PhraseRecognitionEvent event) {
             // A FORCED status implies that recognition is still active after the event.
             event.common.recognitionStillActive |= event.common.status == RecognitionStatus.FORCED;
-            mDelegate.phraseRecognitionCallback(model, event);
+            PhraseRecognitionEventSys phraseRecognitionEventSys = new PhraseRecognitionEventSys();
+            phraseRecognitionEventSys.phraseRecognitionEvent = event;
+            phraseRecognitionEventSys.halEventReceivedMillis = SystemClock.elapsedRealtimeNanos();
+            mDelegate.phraseRecognitionCallback(model, phraseRecognitionEventSys);
         }
 
         @Override
         public void recognitionCallback(int model, RecognitionEvent event) {
             // A FORCED status implies that recognition is still active after the event.
             event.recognitionStillActive |= event.status == RecognitionStatus.FORCED;
-            mDelegate.recognitionCallback(model, event);
+            RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+            recognitionEventSys.recognitionEvent = event;
+            recognitionEventSys.halEventReceivedMillis = SystemClock.elapsedRealtimeNanos();
+            mDelegate.recognitionCallback(model, recognitionEventSys);
         }
 
         @Override
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 0e796d1..2ee4e3c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -16,12 +16,24 @@
 
 package com.android.server.soundtrigger_middleware;
 
-import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.*;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.DETACH;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.FORCE_RECOGNITION;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.GET_MODEL_PARAMETER;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.LOAD_MODEL;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.LOAD_PHRASE_MODEL;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.MODEL_UNLOADED;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.MODULE_DIED;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.QUERY_MODEL_PARAMETER;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.RECOGNITION;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.RESOURCES_AVAILABLE;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.SET_MODEL_PARAMETER;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.START_RECOGNITION;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.STOP_RECOGNITION;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.UNLOAD_MODEL;
 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;
 import android.media.permission.Identity;
 import android.media.permission.IdentityContext;
@@ -29,11 +41,12 @@
 import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.os.BatteryStatsInternal;
 import android.os.IBinder;
@@ -45,19 +58,18 @@
 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 com.android.server.utils.EventLogger.Event;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.Deque;
 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;
 
 
 /**
@@ -370,7 +382,7 @@
         }
 
         @Override
-        public void onRecognition(int modelHandle, RecognitionEvent event, int captureSession)
+        public void onRecognition(int modelHandle, RecognitionEventSys event, int captureSession)
                 throws RemoteException {
             try {
                 mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger(
@@ -388,13 +400,13 @@
         }
 
         @Override
-        public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event,
+        public void onPhraseRecognition(int modelHandle, PhraseRecognitionEventSys event,
                 int captureSession)
                 throws RemoteException {
             try {
                 mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger(
                         SystemClock.elapsedRealtime(), mOriginatorIdentity.uid);
-                startKeyphraseEventLatencyTracking(event);
+                startKeyphraseEventLatencyTracking(event.phraseRecognitionEvent);
                 mCallbackDelegate.onPhraseRecognition(modelHandle, event, captureSession);
                 mEventLogger.enqueue(SessionEvent.createForVoid(
                             RECOGNITION, modelHandle, event, captureSession)
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
index 00cedd7..00b894e 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
@@ -27,15 +27,15 @@
 import android.media.permission.IdentityContext;
 import android.media.permission.PermissionUtil;
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -307,16 +307,15 @@
             }
 
             @Override
-            public void onRecognition(int modelHandle, RecognitionEvent event, int captureSession)
-                    throws RemoteException {
+            public void onRecognition(int modelHandle, RecognitionEventSys event,
+                    int captureSession) throws RemoteException {
                 enforcePermissions("Sound trigger recognition.");
                 mDelegate.onRecognition(modelHandle, event, captureSession);
             }
 
             @Override
-            public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event,
-                    int captureSession)
-                    throws RemoteException {
+            public void onPhraseRecognition(int modelHandle, PhraseRecognitionEventSys event,
+                    int captureSession) throws RemoteException {
                 enforcePermissions("Sound trigger phrase recognition.");
                 mDelegate.onPhraseRecognition(modelHandle, event, captureSession);
             }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 15c9ba9..f208c03 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -21,17 +21,17 @@
 import android.media.permission.Identity;
 import android.media.permission.IdentityContext;
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -710,8 +710,7 @@
             }
         }
 
-        class CallbackWrapper implements ISoundTriggerCallback,
-                IBinder.DeathRecipient {
+        class CallbackWrapper implements ISoundTriggerCallback, IBinder.DeathRecipient {
             private final ISoundTriggerCallback mCallback;
 
             CallbackWrapper(ISoundTriggerCallback callback) {
@@ -728,11 +727,11 @@
             }
 
             @Override
-            public void onRecognition(int modelHandle, @NonNull RecognitionEvent event,
+            public void onRecognition(int modelHandle, @NonNull RecognitionEventSys event,
                     int captureSession) {
                 synchronized (SoundTriggerMiddlewareValidation.this) {
                     ModelState modelState = mLoadedModels.get(modelHandle);
-                    if (!event.recognitionStillActive) {
+                    if (!event.recognitionEvent.recognitionStillActive) {
                         modelState.activityState = ModelState.Activity.LOADED;
                     }
                 }
@@ -744,7 +743,7 @@
                     Log.w(TAG, "Client callback exception.", e);
                     synchronized (SoundTriggerMiddlewareValidation.this) {
                         ModelState modelState = mLoadedModels.get(modelHandle);
-                        if (event.status != RecognitionStatus.FORCED) {
+                        if (event.recognitionEvent.status != RecognitionStatus.FORCED) {
                             modelState.activityState = ModelState.Activity.INTERCEPTED;
                             // If we failed to deliver an actual event to the client, they would
                             // never know to restart it whenever circumstances change. Thus, we
@@ -758,10 +757,10 @@
 
             @Override
             public void onPhraseRecognition(int modelHandle,
-                    @NonNull PhraseRecognitionEvent event, int captureSession) {
+                    @NonNull PhraseRecognitionEventSys event, int captureSession) {
                 synchronized (SoundTriggerMiddlewareValidation.this) {
                     ModelState modelState = mLoadedModels.get(modelHandle);
-                    if (!event.common.recognitionStillActive) {
+                    if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
                         modelState.activityState = ModelState.Activity.LOADED;
                     }
                 }
@@ -773,7 +772,7 @@
                     Log.w(TAG, "Client callback exception.", e);
                     synchronized (SoundTriggerMiddlewareValidation.this) {
                         ModelState modelState = mLoadedModels.get(modelHandle);
-                        if (!event.common.recognitionStillActive) {
+                        if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
                             modelState.activityState = ModelState.Activity.INTERCEPTED;
                             // If we failed to deliver an actual event to the client, they would
                             // never know to restart it whenever circumstances change. Thus, we
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 6223b2e..84cec55 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -19,16 +19,16 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.SoundModelType;
 import android.media.soundtrigger.Status;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -499,10 +499,10 @@
 
             @Override
             public void recognitionCallback(int modelHandle,
-                    @NonNull RecognitionEvent recognitionEvent) {
+                    @NonNull RecognitionEventSys event) {
                 ISoundTriggerCallback callback;
                 synchronized (SoundTriggerModule.this) {
-                    if (!recognitionEvent.recognitionStillActive) {
+                    if (!event.recognitionEvent.recognitionStillActive) {
                         setState(ModelState.LOADED);
                     }
                     callback = mCallback;
@@ -510,7 +510,7 @@
                 // The callback must be invoked outside of the lock.
                 try {
                     if (callback != null) {
-                        callback.onRecognition(mHandle, recognitionEvent, mSession.mSessionHandle);
+                        callback.onRecognition(mHandle, event, mSession.mSessionHandle);
                     }
                 } catch (RemoteException e) {
                     // We're not expecting any exceptions here.
@@ -520,10 +520,10 @@
 
             @Override
             public void phraseRecognitionCallback(int modelHandle,
-                    @NonNull PhraseRecognitionEvent phraseRecognitionEvent) {
+                    @NonNull PhraseRecognitionEventSys event) {
                 ISoundTriggerCallback callback;
                 synchronized (SoundTriggerModule.this) {
-                    if (!phraseRecognitionEvent.common.recognitionStillActive) {
+                    if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
                         setState(ModelState.LOADED);
                     }
                     callback = mCallback;
@@ -532,8 +532,7 @@
                 // The callback must be invoked outside of the lock.
                 try {
                     if (callback != null) {
-                        mCallback.onPhraseRecognition(mHandle, phraseRecognitionEvent,
-                                mSession.mSessionHandle);
+                        mCallback.onPhraseRecognition(mHandle, event, mSession.mSessionHandle);
                     }
                 } catch (RemoteException e) {
                     // We're not expecting any exceptions here.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index 6053f1d..daecfe7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -17,8 +17,8 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
+import android.tools.common.PlatformConsts
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.common.traces.wm.WindowManagerState.Companion.STATE_RESUMED
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.helpers.FIND_TIMEOUT
 import android.tools.device.traces.parsers.WindowManagerStateHelper
@@ -54,8 +54,8 @@
         launchButton.click()
         wmHelper
             .StateSyncBuilder()
-            .withActivityState(SECONDARY_ACTIVITY_COMPONENT, STATE_RESUMED)
-            .withActivityState(MAIN_ACTIVITY_COMPONENT, STATE_RESUMED)
+            .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
             .waitForAndVerify()
     }
 
@@ -73,8 +73,8 @@
         launchButton.click()
         wmHelper
             .StateSyncBuilder()
-            .withActivityState(PLACEHOLDER_PRIMARY_COMPONENT, STATE_RESUMED)
-            .withActivityState(PLACEHOLDER_SECONDARY_COMPONENT, STATE_RESUMED)
+            .withActivityState(PLACEHOLDER_PRIMARY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .withActivityState(PLACEHOLDER_SECONDARY_COMPONENT, PlatformConsts.STATE_RESUMED)
             .waitForAndVerify()
     }
 
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
index 017de60..e2d17cd 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
@@ -26,39 +26,57 @@
 import android.graphics.PixelFormat;
 import android.graphics.RenderNode;
 import android.graphics.Shader;
+import android.graphics.SurfaceTexture;
 import android.hardware.HardwareBuffer;
 import android.media.Image;
 import android.media.ImageWriter;
 import android.os.Bundle;
+import android.view.Surface;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
+import android.view.TextureView;
 import android.view.View;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.Spinner;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
 
 import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.FutureTask;
 
 @SuppressWarnings({"UnusedDeclaration"})
 public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callback,
-        AdapterView.OnItemSelectedListener {
+        TextureView.SurfaceTextureListener {
 
     private static final int WIDTH = 512;
     private static final int HEIGHT = 512;
 
     private ImageView mImageView;
     private SurfaceView mSurfaceView;
+    private TextureView mTextureView;
     private HardwareBuffer mGradientBuffer;
-    private ImageWriter mImageWriter;
+    private Map<View, ImageWriter> mImageWriters = new HashMap<>();
     private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
     private String[] mColorNames = {"sRGB", "BT2020_HLG", "BT2020_PQ"};
-    private String mCurrentColorName = "sRGB";
 
-    private FutureTask<HardwareBuffer> authorGradientBuffer(HardwareBuffer buffer) {
+    private int mGradientEndColor = 0xFFFFFFFF;
+
+    private int[] mGradientEndColors = {0xFFFFFFFF, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF};
+    private String[] mGradientColorNames = {"Grayscale", "Red", "Green", "Blue"};
+
+    private final ExecutorService mBufferFenceExecutor = Executors.newFixedThreadPool(1);
+    private final ExecutorService mBufferExecutor = Executors.newFixedThreadPool(1);
+
+    private FutureTask<HardwareBuffer> authorGradientBuffer(
+            HardwareBuffer buffer, int gradentEndColor) {
         HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer);
         RenderNode node = new RenderNode("content");
         node.setPosition(0, 0, buffer.getWidth(), buffer.getHeight());
@@ -66,9 +84,10 @@
         Canvas canvas = node.beginRecording();
         LinearGradient gradient = new LinearGradient(
                 0, 0, buffer.getWidth(), buffer.getHeight(), 0xFF000000,
-                0xFFFFFFFF, Shader.TileMode.CLAMP);
+                gradentEndColor, Shader.TileMode.CLAMP);
         Paint paint = new Paint();
         paint.setShader(gradient);
+        paint.setDither(true);
         canvas.drawRect(0f, 0f, buffer.getWidth(), buffer.getHeight(), paint);
         node.endRecording();
 
@@ -78,7 +97,7 @@
         FutureTask<HardwareBuffer> resolvedBuffer = new FutureTask<>(() -> buffer);
         renderer.obtainRenderRequest()
                 .setColorSpace(colorSpace)
-                .draw(Executors.newSingleThreadExecutor(), result -> {
+                .draw(mBufferFenceExecutor, result -> {
                     result.getFence().await(Duration.ofSeconds(3));
                     resolvedBuffer.run();
                 });
@@ -90,7 +109,7 @@
                 WIDTH, HEIGHT, PixelFormat.RGBA_8888, 1,
                 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
                         | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT);
-        return authorGradientBuffer(buffer);
+        return authorGradientBuffer(buffer, mGradientEndColor);
     }
 
     @Override
@@ -101,29 +120,70 @@
 
             mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
 
-            ArrayAdapter<String> adapter = new ArrayAdapter<>(
+            ArrayAdapter<String> colorSpaceAdapter = new ArrayAdapter<>(
                     this, android.R.layout.simple_spinner_item, mColorNames);
 
-            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-            Spinner spinner = new Spinner(this);
-            spinner.setAdapter(adapter);
-            spinner.setOnItemSelectedListener(this);
+            colorSpaceAdapter
+                    .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+            Spinner colorSpaceSpinner = new Spinner(this);
+            colorSpaceSpinner.setAdapter(colorSpaceAdapter);
+            colorSpaceSpinner.setOnItemSelectedListener(new ColorSpaceOnItemSelectedListener());
+
+            ArrayAdapter<String> gradientColorAdapter = new ArrayAdapter<>(
+                    this, android.R.layout.simple_spinner_item, mGradientColorNames);
+
+            gradientColorAdapter
+                    .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+            Spinner gradientColorSpinner = new Spinner(this);
+            gradientColorSpinner.setAdapter(gradientColorAdapter);
+            gradientColorSpinner
+                    .setOnItemSelectedListener(new GradientColorOnItemSelectedListener());
 
             mGradientBuffer = getGradientBuffer().get();
 
             LinearLayout linearLayout = new LinearLayout(this);
             linearLayout.setOrientation(LinearLayout.VERTICAL);
 
+            TextView imageViewText = new TextView(this);
+            imageViewText.setText("ImageView");
             mImageView = new ImageView(this);
 
+            TextView textureViewText = new TextView(this);
+            textureViewText.setText("TextureView");
+            mTextureView = new TextureView(this);
+            mTextureView.setSurfaceTextureListener(this);
+
+            TextView surfaceViewText = new TextView(this);
+            surfaceViewText.setText("SurfaceView");
             mSurfaceView = new SurfaceView(this);
             mSurfaceView.getHolder().addCallback(this);
 
-            linearLayout.addView(spinner, new LinearLayout.LayoutParams(
+            LinearLayout spinnerLayout = new LinearLayout(this);
+            spinnerLayout.setOrientation(LinearLayout.HORIZONTAL);
+
+            spinnerLayout.addView(colorSpaceSpinner, new LinearLayout.LayoutParams(
                     LinearLayout.LayoutParams.WRAP_CONTENT,
                     LinearLayout.LayoutParams.WRAP_CONTENT));
 
+            spinnerLayout.addView(gradientColorSpinner, new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT));
+
+            linearLayout.addView(spinnerLayout, new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT));
+
+            linearLayout.addView(imageViewText,  new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT));
             linearLayout.addView(mImageView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
+            linearLayout.addView(textureViewText,  new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT));
+            linearLayout.addView(mTextureView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
+            linearLayout.addView(surfaceViewText,  new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT));
             linearLayout.addView(mSurfaceView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
 
             setContentView(linearLayout);
@@ -145,16 +205,24 @@
     }
 
     private void populateBuffers() {
-        Bitmap bitmap = Bitmap.wrapHardwareBuffer(
-                mGradientBuffer, ColorSpace.get(ColorSpace.Named.SRGB));
-        Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, false);
-        copy.setColorSpace(mColorSpace);
-        mImageView.setImageBitmap(copy);
+        try {
+            Bitmap bitmap = Bitmap.wrapHardwareBuffer(
+                    getGradientBuffer().get(), ColorSpace.get(ColorSpace.Named.SRGB));
+            Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, false);
+            copy.setColorSpace(mColorSpace);
+            mImageView.setImageBitmap(copy);
 
-        try (Image image = mImageWriter.dequeueInputImage()) {
-            authorGradientBuffer(image.getHardwareBuffer()).get();
-            image.setDataSpace(mColorSpace.getDataSpace());
-            mImageWriter.queueInputImage(image);
+            for (ImageWriter writer : mImageWriters.values()) {
+                mBufferExecutor.execute(() -> {
+                    try (Image image = writer.dequeueInputImage()) {
+                        authorGradientBuffer(image.getHardwareBuffer(), mGradientEndColor).get();
+                        image.setDataSpace(mColorSpace.getDataSpace());
+                        writer.queueInputImage(image);
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                });
+            }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -167,30 +235,81 @@
 
     @Override
     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-        mImageWriter = new ImageWriter.Builder(holder.getSurface())
+        mImageWriters.put(mSurfaceView, new ImageWriter.Builder(holder.getSurface())
                 .setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
                         | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT
                         | HardwareBuffer.USAGE_COMPOSER_OVERLAY)
-                .build();
+                .build());
         populateBuffers();
     }
 
     @Override
     public void surfaceDestroyed(SurfaceHolder holder) {
-        mImageWriter.close();
-        mImageWriter = null;
+        if (mImageWriters.containsKey(mSurfaceView)) {
+            mImageWriters.remove(mSurfaceView);
+        }
     }
 
-
     @Override
-    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-        mCurrentColorName = mColorNames[position];
-        mColorSpace = getFromName(mCurrentColorName);
+    public void onSurfaceTextureAvailable(
+            @NonNull SurfaceTexture surface, int width, int height) {
+        mImageWriters.put(mTextureView, new ImageWriter.Builder(new Surface(surface))
+                .setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+                        | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT)
+                .build());
         populateBuffers();
     }
 
     @Override
-    public void onNothingSelected(AdapterView<?> parent) {
+    public void onSurfaceTextureSizeChanged(
+            @NonNull SurfaceTexture surface, int width, int height) {
 
     }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
+        if (mImageWriters.containsKey(mTextureView)) {
+            mImageWriters.remove(mTextureView);
+        }
+        return false;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
+
+    }
+
+    private final class ColorSpaceOnItemSelectedListener
+            implements AdapterView.OnItemSelectedListener {
+
+        @Override
+        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+            ColorBitmapActivity.this.mColorSpace =
+                    getFromName(ColorBitmapActivity.this.mColorNames[position]);
+            ColorBitmapActivity.this.getMainExecutor()
+                    .execute(ColorBitmapActivity.this::populateBuffers);
+        }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> parent) {
+
+        }
+    }
+
+    private final class GradientColorOnItemSelectedListener
+            implements AdapterView.OnItemSelectedListener {
+
+        @Override
+        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+            ColorBitmapActivity.this.mGradientEndColor =
+                    ColorBitmapActivity.this.mGradientEndColors[position];
+            ColorBitmapActivity.this.getMainExecutor()
+                    .execute(ColorBitmapActivity.this::populateBuffers);
+        }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> parent) {
+
+        }
+    }
 }